sergkulich/laravel-key-rotate

A Laravel package to rotate app key and re-encrypt data stored in eloquent models.

1.0.0 2024-12-22 13:03 UTC

This package is auto-updated.

Last update: 2024-12-22 14:19:07 UTC


README

Latest Version on Packagist Total Downloads

Laravel Key Rotate package allows automatically copy current APP_KEY and update APP_PREVIOUS_KEYS with it, After that it will call key:generate artisan command to generate new APP_KEY, and, optionally, update all your Eloquent Models encrypted fields.

The Command available only then app()->runningInConsole() as it may take a while to update Models.

Warning

The package is in beta. Use with caution.

Never run in production without previous local testing and fresh .env copy and DB dump.

Table of contents

Installation

Install the package via composer:

composer require sergkulich/laravel-key-rotate

Usage

Run artisan command to rotate the key.

php artisan key:rotate

Use --force option to run the command in production to avoid confirmation prompt.

php artisan key:rotate --force

Dive deeper

Quick example of extended usage.

You need to register KeyRotate::useListener() only if you want your model encrypted fields to be re-encrypted.

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if (app()->runningInConsole()) {
            // Subscribe The Listener to The Event
            KeyRotate::withListener()
                // Discover Models that are not in App\ namespace
                ->withDir(base_path('src/Domain'), 'Domain\\')
                // Exclude Models
                ->withoutModel(User::class)
                // Exclude Model Fields
                ->withoutModelFileds(Secret::class, ['secret'])
                // Without Cast
                ->withoutCast('encrypted:array')
                // With Custom Cast
                ->withCast(CustomCast::class);
        }
    }
}

The Event

The key:rotate command fires an event after successful completion.

The Event Class

The package provides helper to get the event class name to subscribe listeners to it.

$keyRotateEventClass = KeyRotate::getEventClass();

Event::listen($keyRotateEventClass, MyKeyRotateListener::class);

The Listener

The package provides default listener that update encrypted models fields after key rotation.

The Listener Class

The package provides helper to get the listener class name in case you need it.

KeyRotate::getListenerClass();

Get Instance of the Listener

Simply subscribe the listener to the event in your app service provider boot method.

namespace App\Providers;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        if (app()->runningInConsole()) {
            KeyRotate::withListener();
        }
    }
}

Or get singleton instance of the listener and call it anywhere in your code.

// Get Instance
$listener = KeyRotate::getListener();

// Call it either
$listener();
// or
$listener->handle();

Models

The package discovers all Models in your app()->path() folder with app()->getNamespace() namespace.

But only then app()->runningInConsole(), so no unnecessary file scans happen during http requests.

Discover Models

It's possible to discover Models with defined namespace that are not located in your app()->path() but in a custom directory.

$listener->withDir(base_path('src/Domain'), 'NameSpace\\Domain\\');

Include Models

It's possible to include Model to be updated with the listener without scanning directories.

$listener->withModel(CustomModel::class);

Exclude Models

It's possible to exclude Model from being updated with the listener.

$listener->withoutModel(User::class);

Exclude Model Fields

It's possible to exclude some Model fields from being updated with the listener.

$listener->withoutModelFields(User::class, ['encrypted_text', 'encrypted_array']);

Casts

By default, the listener takes care of Laravel's predefined encrypted Casts.

[
    'encrypted',
    'encrypted:array',
    'encrypted:collection',
    'encrypted:object',
    AsEncryptedArrayObject::class,
    AsEncryptedCollection::class,
];

At your disposal there are helpers to reduce the above list or extend it with your custom cast that implements Castable, CastsAttributes, or CastsInboundAttributes interfaces.

// Exclude cast
$listener->withoutCast('encrypted:array');

// Include cast
$listener->withCast(CustomCast::class);

Tests

Run the entire test suite:

composer test

Changelog

Please see CHANGELOG for more information.

Contributing

Please see CONTRIBUTING for more information.

License

The MIT License (MIT). Please see LICENSE for more information.