diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..52e54d8
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,30 @@
+name: Run Tests
+
+on: ['push', 'pull_request']
+
+jobs:
+ ci:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ php: ['7.4', '8.0', '8.1']
+
+ name: PHP ${{ matrix.php }} - ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ tools: composer:v2
+ coverage: xdebug
+
+ - name: Install PHP dependencies
+ run: composer update --no-interaction --no-progress
+
+ - name: All Tests
+ run: php vendor/bin/pest --colors=always --coverage
diff --git a/.gitignore b/.gitignore
index f56cefe..7f40cfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,17 @@
-test
-Experimental
-vendor
-composer.lock
\ No newline at end of file
+# Global
+.phpunit*
+.composer
+composer.lock
+package-lock.json
+vendor/
+test/
+
+# OS Generated
+.DS_Store*
+ehthumbs.db
+Icon?
+Thumbs.db
+*.swp
+
+# phpstorm
+.idea/*
diff --git a/README.md b/README.md
index 253cc48..46294c3 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,17 @@
-
-
Leaf Security Module
+
-# Leaf PHP
+# Leaf Anchor
[![Latest Stable Version](https://poser.pugx.org/leafs/anchor/v/stable)](https://packagist.org/packages/leafs/anchor)
[![Total Downloads](https://poser.pugx.org/leafs/anchor/downloads)](https://packagist.org/packages/leafs/anchor)
[![License](https://poser.pugx.org/leafs/anchor/license)](https://packagist.org/packages/leafs/anchor)
-This package contains leaf's utils for deep sanitizing of data and basic security provided for your app data.
+This package contains leaf's utils for deep sanitizing of data and basic security provided for your app data. It also serves as the base for security provided in other modules like CSRF.
## Installation
@@ -54,5 +53,3 @@ You may quickly test this using the built-in PHP server:
```bash
php -S localhost:8000
```
-
-Built with ❤ by [**Mychi Darko**](https://mychi.netlify.app)
diff --git a/composer.json b/composer.json
index 14bfe97..0a31140 100644
--- a/composer.json
+++ b/composer.json
@@ -24,5 +24,8 @@
}
},
"minimum-stability": "dev",
- "prefer-stable": true
-}
\ No newline at end of file
+ "prefer-stable": true,
+ "require-dev": {
+ "pestphp/pest": "^1.21"
+ }
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..4e6c4a9
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./app
+ ./src
+
+
+
diff --git a/src/Anchor.php b/src/Anchor.php
index b53745c..9616aef 100755
--- a/src/Anchor.php
+++ b/src/Anchor.php
@@ -1,5 +1,7 @@
"_token",
- "SECRET" => "@nkor_leaf$0Secret!",
- "EXCEPT" => [],
- "METHODS" => ["POST", "PUT", "PATCH", "DELETE"],
+ 'SECRET_KEY' => '_token',
+ 'SECRET' => '@nkor_leaf$0Secret!',
+ 'EXCEPT' => [],
+ 'METHODS' => ['POST', 'PUT', 'PATCH', 'DELETE'],
];
protected static $errors = [];
@@ -45,7 +47,7 @@ public static function sanitize($data)
{
if (is_array($data)) {
foreach ($data as $key => $value) {
- $data[self::sanitize($key)] = self::sanitize($value);
+ $data[is_string($key) ? self::sanitize($key) : $key] = self::sanitize($value);
}
} else {
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
@@ -56,15 +58,15 @@ public static function sanitize($data)
/**
* Generate a token for identifying your application
+ *
+ * @param int $strength Number of random characters to attach to token
*/
- public static function generateToken()
+ public static function generateToken(int $strength = 16): string
{
- $token = base64_encode(static::$config["SECRET"] . random_bytes(16));
-
- return $token;
+ return bin2hex(static::$config['SECRET'] . '.' . random_bytes($strength));;
}
- public static function errors()
+ public static function errors(): array
{
return static::$errors;
}
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..5949c61
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,45 @@
+in('Feature');
+
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
+
+expect()->extend('toBeOne', function () {
+ return $this->toBe(1);
+});
+
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
+
+function something()
+{
+ // ..
+}
diff --git a/tests/anchor.test.php b/tests/anchor.test.php
new file mode 100644
index 0000000..236ef60
--- /dev/null
+++ b/tests/anchor.test.php
@@ -0,0 +1,46 @@
+ 'item']);
+ $config = Anchor::config();
+
+ expect($config['SECRET'])->toBe('item');
+});
+
+test('sanitize', function () {
+ $html = 'Hello World';
+
+ expect(Anchor::sanitize($html))->toBe(htmlspecialchars($html));
+});
+
+test('sanitize array', function () {
+ $html = ['Hello World', 'Hello World'];
+
+ expect(Anchor::sanitize($html))->toBe([
+ htmlspecialchars('Hello World'),
+ htmlspecialchars('Hello World'),
+ ]);
+});
+
+test('sanitize assoc array', function () {
+ $html = ['key' => 'Hello World'];
+
+ expect(Anchor::sanitize($html))->toBe(['key' => htmlspecialchars('Hello World')]);
+});
+
+test('generate token', function () {
+ expect(Anchor::generateToken())->toBeString();
+});
+
+test('secret in token', function () {
+ $anchorSecret = 'SOMETHING';
+ Anchor::config(['SECRET' => $anchorSecret]);
+
+ expect(strpos(hex2bin(Anchor::generateToken()), $anchorSecret))->toBe(0);
+});
+
+test('errors', function () {
+ expect(Anchor::errors())->toBeArray();
+});