Skip to content

Commit 8a0bbde

Browse files
committed
:octocat: PHP 8.2+
1 parent 41fcc74 commit 8a0bbde

10 files changed

Lines changed: 73 additions & 70 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,14 @@ jobs:
3838
fail-fast: true
3939
matrix:
4040
php-version:
41-
- "7.4"
42-
- "8.0"
43-
- "8.1"
4441
- "8.2"
4542
- "8.3"
4643
- "8.4"
47-
# - "8.5"
44+
- "8.5"
4845

4946
steps:
5047
- name: "Checkout"
51-
uses: actions/checkout@v4
48+
uses: actions/checkout@v5
5249

5350
- name: "Install PHP"
5451
uses: shivammathur/setup-php@v2
@@ -83,17 +80,14 @@ jobs:
8380
- ubuntu-latest
8481
- windows-latest
8582
php-version:
86-
- "7.4"
87-
- "8.0"
88-
- "8.1"
8983
- "8.2"
9084
- "8.3"
9185
- "8.4"
9286
- "8.5"
9387

9488
steps:
9589
- name: "Checkout"
96-
uses: actions/checkout@v4
90+
uses: actions/checkout@v5
9791

9892
- name: "Install PHP with extensions"
9993
uses: shivammathur/setup-php@v2

.phan/config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// that functions removed in php 7.0 exist.
1414
// (See `backward_compatibility_checks` for additional options)
1515
'target_php_version' => null,
16-
'minimum_target_php_version' => '7.4',
16+
'minimum_target_php_version' => '8.2',
1717

1818
// A list of directories that should be parsed for class and
1919
// method information. After excluding the directories

README.md

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ An authenticator ([chillerlan/php-authenticator](https://github.com/chillerlan/p
2121

2222
## Requirements
2323

24-
- PHP 7.4+
24+
- PHP 8.2+
2525
- [`ext-mbstring`](https://www.php.net/manual/book.mbstring.php)
2626
- optional:
2727
- [`ext-gd`](https://www.php.net/manual/book.image) for `QRGdImage` based output
@@ -35,27 +35,27 @@ You can find the documentation of the bundled libraries in their respective repo
3535

3636
- [chillerlan/php-authenticator](https://github.com/chillerlan/php-authenticator)
3737
- [chillerlan/php-qrcode](https://github.com/chillerlan/php-qrcode)
38-
- User manual: https://php-qrcode.readthedocs.io/
38+
- User manual: https://php-qrcode.readthedocs.io/en/v6.0.x/
3939
- API documentation: https://chillerlan.github.io/php-qrcode/
4040
- [chillerlan/php-settings-container](https://github.com/chillerlan/php-settings-container)
4141

4242
### User Guide
4343

4444
#### Invocation
4545

46-
Fetch the settings from e.g. a framework config and [invoke the `TwoFactorQRCodeOptions` instance](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Advanced-usage.html#configuration-via-qroptions) with it.
47-
Please note that this object combines the settings for [`AuthenticatorOptions`](https://github.com/chillerlan/php-authenticator?tab=readme-ov-file#authenticatoroptions) and [`QROptions`](https://php-qrcode.readthedocs.io/en/v5.0.x/Usage/Configuration-settings.html).
46+
Fetch the settings from e.g. a framework config and [invoke the `TwoFactorQRCodeOptions` instance](https://php-qrcode.readthedocs.io/en/v6.0.x/Usage/Advanced-usage.html#configuration-via-qroptions) with it.
47+
Please note that this object combines the settings for [`AuthenticatorOptions`](https://github.com/chillerlan/php-authenticator?tab=readme-ov-file#authenticatoroptions) and [`QROptions`](https://php-qrcode.readthedocs.io/en/v6.0.x/Usage/Configuration-settings.html).
48+
Alternatively, you can just pass an `iterable` of options to the `TwoFactorQRCode` constructor.
4849

4950
```php
50-
use chillerlan\TwoFactorQRCode\{TwoFactorQRCode, TwoFactorQRCodeOptions};
51+
use chillerlan\TwoFactorQRCode\TwoFactorQRCode;
5152

5253
$options = [
5354
'secret_length' => 128,
5455
'adjacent' => 2,
5556
];
5657

57-
$twoFactorQRCodeOptions = new TwoFactorQRCodeOptions($options);
58-
$twoFactorQRCode = new TwoFactorQRCode($twoFactorQRCodeOptions);
58+
$twoFactorQRCode = new TwoFactorQRCode($options);
5959
```
6060

6161
#### User registration
@@ -100,7 +100,6 @@ The user has lost access to their authenticator, send them to a form separate fr
100100
- Verify the given backup OTP against the stored counter value.
101101
- After verification, increase the counter, create a new backup code and save the new counter value.
102102
- Present the new backup code to the user and make them triple check that they have carefully saved it.
103-
- Redirect the user to wherever they can manage their 2FA settings and retrieve their secret once again.
104103

105104
```php
106105
$twoFactorQRCode->setSecret($userSecret);
@@ -114,6 +113,15 @@ if($twoFactorQRCode->verifyBackupCode($backupOTP, $counterValue)){
114113
}
115114
```
116115

116+
Redirect the user to wherever they can manage their 2FA settings and retrieve their secret once again:
117+
118+
```php
119+
$twoFactorQRCode->setSecret($userSecret);
120+
121+
$currentBackup = $twoFactorQRCode->createBackupCode($currentCounterValue);
122+
$qrcode = $twoFactorQRCode->getQRCode('label', 'issuer');
123+
```
124+
117125
#### Validating an e-mail address
118126

119127
The previous flow can also be used for other tasks, such as e-mail verification:
@@ -158,18 +166,18 @@ However, some of [the top used libraries on packagist](https://packagist.org/sea
158166

159167
The class is not final and you're free to extend it to add/change functionality to your liking.
160168

161-
| method | return | description |
162-
|-----------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------------|
163-
| `__construct(SettingsContainerInterface\|null $options = null)` | - | |
164-
| `createSecret(int\|null $length = null)` | `string` | Creates a cryptograpically secure random secret and returns it as Base32 encoded string |
165-
| `setSecret(string $encodedSecret)` | `static` | Sets a secret phrase from an encoded representation |
166-
| `getSecret()` | `string` | Returns an encoded representation of the current secret phrase |
167-
| `setRawSecret(string $rawSecret)` | `static` | Sets a secret phrase from a raw binary representation |
168-
| `getRawSecret()` | `string` | Returns the raw representation of the current secret phrase |
169-
| `verifyOTP(string $otp, int\|null $timestamp = null)` | `bool` | Verifies a one-time password (TOTP) with an optional unix timestamp |
170-
| `createBackupCode(int $counter)` | `string` | Creates a counter-based one-time password (HOTP) from the given counter value |
171-
| `verifyBackupCode(string $otp, int $counter)` | `bool` | Verifies a counter-based backup code (HOTP) against the given counter value |
172-
| `getQRCode(string $label, string $issuer)` | `string` | Creates a QR Code for use with a mobile authenticator application (TOTP) with the given label and issuer name |
169+
| method | return | description |
170+
|--------------------------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------------|
171+
| `__construct(SettingsContainerInterface\|TwoFactorQRCodeOptions \|iterable $options = new TwoFactorQRCodeOptions)` | - | |
172+
| `createSecret(int\|null $length = null)` | `string` | Creates a cryptograpically secure random secret and returns it as Base32 encoded string |
173+
| `setSecret(string $encodedSecret)` | `static` | Sets a secret phrase from an encoded representation |
174+
| `getSecret()` | `string` | Returns an encoded representation of the current secret phrase |
175+
| `setRawSecret(string $rawSecret)` | `static` | Sets a secret phrase from a raw binary representation |
176+
| `getRawSecret()` | `string` | Returns the raw representation of the current secret phrase |
177+
| `verifyOTP(string $otp, int\|null $timestamp = null)` | `bool` | Verifies a one-time password (TOTP) with an optional unix timestamp |
178+
| `createBackupCode(int $counter)` | `string` | Creates a counter-based one-time password (HOTP) from the given counter value |
179+
| `verifyBackupCode(string $otp, int $counter)` | `bool` | Verifies a counter-based backup code (HOTP) against the given counter value |
180+
| `getQRCode(string $label, string $issuer)` | `string` | Creates a QR Code for use with a mobile authenticator application (TOTP) with the given label and issuer name |
173181

174182

175183
## Anti clanker policy

composer.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@
3333
"minimum-stability": "stable",
3434
"prefer-stable": true,
3535
"require": {
36-
"php": "^7.4 || ^8.0",
37-
"chillerlan/php-authenticator": "^4.5.0",
38-
"chillerlan/php-qrcode": "^5.0.5",
39-
"chillerlan/php-settings-container": "^2.1.6"
36+
"php": "^8.2",
37+
"chillerlan/php-authenticator": "^5.4.0",
38+
"chillerlan/php-qrcode": "^6.0",
39+
"chillerlan/php-settings-container": "^3.2.1"
4040
},
4141
"require-dev": {
42-
"phan/phan": "^5.5.2",
42+
"phan/phan": "^6.0.2",
4343
"phpmd/phpmd": "^2.15",
44-
"phpunit/phpunit": "^9.6",
44+
"phpunit/phpunit": "^11.5",
4545
"slevomat/coding-standard": "^8.28",
4646
"squizlabs/php_codesniffer": "^4.0"
4747
},

examples/example.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
declare(strict_types=1);
1111

1212
use chillerlan\TwoFactorQRCode\TwoFactorQRCode;
13-
use chillerlan\TwoFactorQRCode\TwoFactorQRCodeOptions;
1413

1514
require_once __DIR__.'/../vendor/autoload.php';
1615

@@ -24,9 +23,8 @@
2423
'adjacent' => 2,
2524
];
2625

27-
// invoke settings and TwoFactorQRCode instance
28-
$twoFactorQRCodeOptions = new TwoFactorQRCodeOptions($options);
29-
$twoFactorQRCode = new TwoFactorQRCode($twoFactorQRCodeOptions);
26+
// invoke TwoFactorQRCode instance
27+
$twoFactorQRCode = new TwoFactorQRCode($options);
3028

3129

3230
/**

phpcs.xml.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111

112112
<rule ref="SlevomatCodingStandard.TypeHints.DNFTypeHintFormat">
113113
<properties>
114-
<property name="shortNullable" value="true"/>
114+
<property name="shortNullable" value="false"/>
115115
<property name="nullPosition" value="last"/>
116116
</properties>
117117
</rule>

phpunit.xml.dist

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
1+
<?xml version="1.0"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
33
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
44
bootstrap="vendor/autoload.php"
5-
cacheResultFile=".build/phpunit.result.cache"
5+
cacheDirectory=".build/phpunit-cache"
66
colors="true"
7-
verbose="true"
87
>
98
<testsuites>
10-
<testsuite name="php-authenticator test suite">
9+
<testsuite name="chillerlan test suite">
1110
<directory>tests</directory>
1211
</testsuite>
1312
</testsuites>
14-
<coverage processUncoveredFiles="true">
13+
<source>
1514
<include>
16-
<directory suffix=".php">src</directory>
15+
<directory>src</directory>
1716
</include>
17+
</source>
18+
<coverage>
1819
<report>
1920
<clover outputFile=".build/coverage/clover.xml"/>
2021
<xml outputDirectory=".build/coverage/coverage-xml"/>

src/TwoFactorQRCode.php

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,30 @@
1414
use chillerlan\QRCode\QRCode;
1515
use chillerlan\Authenticator\Authenticators\{HOTP, TOTP};
1616
use chillerlan\Settings\SettingsContainerInterface;
17+
use SensitiveParameter;
18+
use function is_iterable;
1719

1820
class TwoFactorQRCode{
1921

2022
protected SettingsContainerInterface $options;
2123
protected HOTP $hotp;
2224
protected TOTP $totp;
2325

24-
/**
25-
* @param \chillerlan\Settings\SettingsContainerInterface|\chillerlan\TwoFactorQRCode\TwoFactorQRCodeOptions|null $options
26-
*/
27-
public function __construct(?SettingsContainerInterface $options = null){
28-
$this->options = ($options ?? new TwoFactorQRCodeOptions);
26+
public function __construct(
27+
SettingsContainerInterface|TwoFactorQRCodeOptions|iterable $options = new TwoFactorQRCodeOptions,
28+
){
29+
30+
if(is_iterable($options)){
31+
$options = new TwoFactorQRCodeOptions($options);
32+
}
33+
34+
$this->options = $options;
2935
$this->hotp = new HOTP($this->options);
3036
$this->totp = new TOTP($this->options);
3137
}
3238

3339
/**
34-
* Creates a cryptograpically secure random secret and returns it as Base32 encoded string.
40+
* Creates a cryptographically secure random secret and returns it as Base32 encoded string.
3541
*
3642
* Note: The secret length is the length of the raw binary string,
3743
* the Base32 encoded string is considerably longer (~60%).
@@ -40,7 +46,7 @@ public function __construct(?SettingsContainerInterface $options = null){
4046
* @see \random_bytes()
4147
* @see \ParagonIE\ConstantTime\Base32
4248
*/
43-
public function createSecret(?int $length = null):string{
49+
public function createSecret(int|null $length = null):string{
4450
$secret = $this->hotp->createSecret($length);
4551

4652
$this->totp->setSecret($secret);
@@ -51,7 +57,7 @@ public function createSecret(?int $length = null):string{
5157
/**
5258
* Sets a secret phrase from an encoded representation
5359
*/
54-
public function setSecret(string $encodedSecret):self{
60+
public function setSecret(#[SensitiveParameter] string $encodedSecret):static{
5561
$this->hotp->setSecret($encodedSecret);
5662
$this->totp->setSecret($encodedSecret);
5763

@@ -68,7 +74,7 @@ public function getSecret():string{
6874
/**
6975
* Sets a secret phrase from a raw binary representation
7076
*/
71-
public function setRawSecret(string $rawSecret):self{
77+
public function setRawSecret(#[SensitiveParameter] string $rawSecret):static{
7278
$this->hotp->setRawSecret($rawSecret);
7379
$this->totp->setRawSecret($rawSecret);
7480

@@ -87,7 +93,7 @@ public function getRawSecret():string{
8793
*
8894
* @see \chillerlan\Authenticator\Authenticators\TOTP
8995
*/
90-
public function verifyOTP(string $otp, ?int $timestamp = null):bool{
96+
public function verifyOTP(string $otp, int|null $timestamp = null):bool{
9197
return $this->totp->verify($otp, $timestamp);
9298
}
9399

src/TwoFactorQRCodeOptions.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
namespace chillerlan\TwoFactorQRCode;
1313

1414
use chillerlan\Authenticator\AuthenticatorOptionsTrait;
15+
use chillerlan\QRCode\QRCodeReaderOptionsTrait;
1516
use chillerlan\QRCode\QROptionsTrait;
1617
use chillerlan\Settings\SettingsContainerAbstract;
1718

1819
class TwoFactorQRCodeOptions extends SettingsContainerAbstract{
19-
use AuthenticatorOptionsTrait, QROptionsTrait;
20+
use AuthenticatorOptionsTrait, QROptionsTrait, QRCodeReaderOptionsTrait;
2021
}

tests/TwoFactorQRCodeTest.php

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
use chillerlan\Authenticator\Common\Base32;
1515
use chillerlan\QRCode\Common\GDLuminanceSource;
1616
use chillerlan\QRCode\Decoder\Decoder;
17-
use chillerlan\QRCode\Output\QROutputInterface;
17+
use chillerlan\QRCode\Output\QRGdImagePNG;
1818
use chillerlan\TwoFactorQRCode\TwoFactorQRCode;
1919
use chillerlan\TwoFactorQRCode\TwoFactorQRCodeOptions;
2020
use Generator;
21+
use PHPUnit\Framework\Attributes\DataProvider;
2122
use PHPUnit\Framework\TestCase;
2223
use function date;
2324
use function is_int;
@@ -94,9 +95,7 @@ public static function totpVectors():Generator{
9495
}
9596
}
9697

97-
/**
98-
* @dataProvider totpVectors
99-
*/
98+
#[DataProvider('totpVectors')]
10099
public function testVerifyOTP(int $timestamp, string $totp):void{
101100
$this->twoFactorQRCodeOptions->digits = 8;
102101

@@ -109,24 +108,20 @@ public static function hotpVectors():Generator{
109108
}
110109
}
111110

112-
/**
113-
* @dataProvider hotpVectors
114-
*/
111+
#[DataProvider('hotpVectors')]
115112
public function testCreateBackupCode(int $counter, string $hotp):void{
116113
$this::assertSame($hotp, $this->twoFactorQRCode->createBackupCode($counter));
117114
}
118115

119-
/**
120-
* @dataProvider hotpVectors
121-
*/
116+
#[DataProvider('hotpVectors')]
122117
public function testVerifyBackupCode(int $counter, string $hotp):void{
123118
$this::assertTrue($this->twoFactorQRCode->verifyBackupCode($hotp, $counter));
124119
}
125120

126121
public function testGetQRCode():void{
127122
/** @phan-suppress-next-line PhanDeprecatedClassConstant */
128-
$this->twoFactorQRCodeOptions->outputType = QROutputInterface::GDIMAGE_PNG;
129-
$this->twoFactorQRCodeOptions->outputBase64 = false;
123+
$this->twoFactorQRCodeOptions->outputInterface = QRGdImagePNG::class;
124+
$this->twoFactorQRCodeOptions->outputBase64 = false;
130125

131126
$qrcode = $this->twoFactorQRCode->getQRCode('testLabel', 'testIssuer');
132127
$result = (new Decoder)->decode(GDLuminanceSource::fromBlob($qrcode));

0 commit comments

Comments
 (0)