Skip to content

Commit

Permalink
Merge pull request #1 from itexia/feature/add-recovery-codes
Browse files Browse the repository at this point in the history
Feature/add recovery codes
  • Loading branch information
susgo authored Jul 7, 2020
2 parents 76ae332 + b146679 commit f8362b7
Show file tree
Hide file tree
Showing 11 changed files with 556 additions and 33 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: PHP Composer and PHP unit

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:

runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ubuntu-latest]
php-versions: ['7.2', '7.3', '7.4']
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}

steps:
- uses: actions/checkout@v2

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
#extensions: intl #optional
#ini-values: "post_max_size=256M" #optional

- name: Check PHP Version
run: php -v

- name: Validate composer.json and composer.lock
run: composer validate

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest

- name: Run tests
run: vendor/bin/phpunit
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4
dist: trusty
cache:
directories:
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@
}
],
"require": {
"php": "^7.2",
"robthree/twofactorauth": "^1.5.1"
},
"require-dev": {
"hiqdev/hidev-php": "dev-master",
"hiqdev/hidev-hiqdev": "dev-master"
"hiqdev/hidev-hiqdev": "dev-master",
"phpunit/phpunit": "^6.5"
},
"autoload": {
"psr-4": {
Expand Down
81 changes: 81 additions & 0 deletions src/base/Recovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);

namespace hiqdev\yii2\mfa\base;

use Yii;
use yii\db\ActiveRecord;

class Recovery extends ActiveRecord
{
public static function tableName()
{
return '{{mfa_recovery_codes}}';
}

public function rules(): array
{
return [
['user_id', 'integer'],
['user_id', 'required'],
['code', 'string'],
['code', 'required'],
];
}

/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
'code' => Yii::t('mfa', 'Recovery code'),
];
}

public function setUser(int $id): self {
$this->user_id = $id;

return $this;
}

public function getUser(): int {
return $this->user_id;
}

public function setCode(string $code): self {
$this->code = $code;

return $this;
}

public function getCode(): string {
return $this->code;
}

public function save($runValidation = true, $attributeNames = null): bool
{
$this->code = $this->hashCode($this->code);

return parent::save($runValidation, $attributeNames);
}

public function verifyCode(): bool
{
$recoveryCodes = self::findAll(['user_id' => $this->getUser()]);

foreach ($recoveryCodes ?? [] as $recoveryCode){
if (Yii::$app->getSecurity()->validatePassword($this->getCode(), $recoveryCode->getCode())){
$recoveryCode->delete();
return true;
}
}

return false;
}

private function hashCode(string $code): string
{
return Yii::$app->security->generatePasswordHash($code);
}
}
120 changes: 120 additions & 0 deletions src/base/RecoveryCodeCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);

namespace hiqdev\yii2\mfa\base;

use Yii;
use yii\base\BaseObject;

class RecoveryCodeCollection extends BaseObject
{
protected array $codes = [];

protected int $count = 10;

protected int $blocks = 4;

protected int $blockLength = 3;

protected string $blockSeparator = '-';

public function getCodes(): array
{
return $this->codes;
}

public function setCount(int $count): void
{
$this->count = $count;
}

public function getCount(): int
{
return $this->count;
}

public function setBlocks(int $blocks): void
{
$this->blocks = $blocks;
}

public function getBlocks(): int
{
return $this->blocks;
}

public function setBlockLength(int $length): void
{
$this->blockLength = $length;
}

public function getBlockLength(): int
{
return $this->blockLength;
}

public function setBlockSeparator(string $separator): void
{
$this->blockSeparator = $separator;
}

public function getBlockSeparator(): string
{
return $this->blockSeparator;
}

public function generate(): self
{
$this->reset();
foreach (range(1, $this->getCount()) as $counter) {
$this->codes[] = $this->generateCode();
}

return $this;
}

public function save(): bool
{
$userId = Yii::$app->user->identity->id;
$this->remove();

$errors = [];
foreach ($this->getCodes() as $code){
$recovery = new Recovery();
if (!$recovery->setUser($userId)->setCode($code)->save()){
$errors[] = $recovery->getErrors();
}
}

return empty($errors);
}

public function remove(): int {
$userId = Yii::$app->user->identity->id;

return Recovery::deleteAll(['user_id' => $userId]);
}

private function generateCode(): string
{
$codeBlocks = [];
foreach (range(1, $this->getBlocks()) as $counter) {
$codeBlocks[] = $this->generateBlock();
}

return implode($this->getBlockSeparator(), $codeBlocks);
}

private function generateBlock(): string
{
return bin2hex(random_bytes($this->getBlockLength()));
}

private function reset(): self
{
$this->codes = [];

return $this;
}

}
Loading

0 comments on commit f8362b7

Please sign in to comment.