Search by

sashalenz / passcode-lock

sashalenz

Laravel passcode lock with morph relationship, idle timeout and keyboard shortcut

Package info

github.com/sashalenz/passcode-lock

pkg:composer/sashalenz/passcode-lock

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-09-22 13:44 UTC

This package is auto-updated.

Last update: 2026-09-22 13:55:01 UTC


README

Laravel package for passcode (4-digit PIN) lock screen with morph relationship to any model, auto-lock on inactivity, and manual lock via keyboard shortcut.

Requirements

  • PHP ^8.5
  • Laravel ^12.0|^13.0
  • Livewire ^4.2
  • xitedev/wireforms ^3.3
  • Alpine.js (loaded via Vite)
  • Tailwind CSS v4

Designed for projects with Livewire + Wireforms + Alpine.js + Tailwind.

Installation

composer require sashalenz/passcode-lock

Publish config (optional, to override defaults):

php artisan vendor:publish --tag=passcode-lock-config

Implementation

1. Migrations

The package registers create_passcodes_table migration. The passcodes table is created when you run:

php artisan migrate

Table structure:

  • id
  • passcodeable_type, passcodeable_id (morph)
  • code (hash)
  • timeout_minutes (default 10)
  • timestamps

2. Model with passcode

Add the HasPasscodeLock trait to the model that should have a passcode (e.g. User or Admin):

use Sashalenz\PasscodeLock\Traits\HasPasscodeLock;

class Admin extends Authenticatable
{
    use HasPasscodeLock;
    // ...
}

Trait methods:

Method Description
passcode() MorphOne to Passcode
hasPasscodeEnabled(): bool Whether passcode is set
getPasscodeTimeoutMinutes(): int Inactivity timeout (minutes)
verifyPasscode(string $plainCode): bool Verify entered code
setPasscode(?string $plainCode, int $timeoutMinutes = 10): void Set or clear passcode
updatePasscodeTimeout(int $timeoutMinutes): void Change timeout only

Passcode is 4 digits; hashed via Laravel Hash.

3. Configuration

File config/passcode-lock.php (or published):

  • guard — single guard name (e.g. admin, web). Used if guards is not set.
  • guards — array of guards for multi-guard passcode lock (e.g. ['admin', 'web']). Overrides guard when set. Session state (last_activity, locked_at) is stored separately per guard; on lock, locked_guard is saved for unlock.
  • timeout_minutes — default inactivity timeout (minutes).
  • session_keys — session keys for last_activity, locked_at, unlocked_at, locked_guard. With multiple guards, keys (except locked_guard) get a suffix.
  • allowed_route_names_when_locked — route names allowed when locked.
  • view — lock screen view (default passcode-lock::lock).
  • redirect_after_unlock — route after unlock (string or array per guard: ['admin' => 'admin.index', 'web' => 'home']).
  • login_route, logout_route — login/logout routes (string or array per guard).

Environment variables (optional):

  • PASSCODE_LOCK_GUARD — single guard
  • PASSCODE_LOCK_GUARDS — multiple guards, comma-separated, e.g. admin,web
  • PASSCODE_TIMEOUT_MINUTES
  • PASSCODE_LOCK_IDLE_CHECK_MS — idle check interval in ms (default 30000)

Multiple guards

To use passcode lock for both admin and web user (or other guards):

  1. Set guards in config (or PASSCODE_LOCK_GUARDS=admin,web).
  2. Models for each guard (e.g. Admin, User) must use the HasPasscodeLock trait.
  3. Set redirect_after_unlock and, if needed, login_route as arrays per guard.
  4. Middleware detects the current guard; on lock it stores locked_guard, on unlock it uses that guard.

4. Middleware

Add middleware to the web group (e.g. in bootstrap/app.php):

use Sashalenz\PasscodeLock\Http\Middleware\PasscodeLockMiddleware;

$middleware->appendToGroup('web', [
    // ...
    PasscodeLockMiddleware::class,
]);

Or via alias:

$middleware->appendToGroup('web', ['passcode-lock']);

Middleware:

  • Runs only for users of configured guard(s).
  • Requires hasPasscodeEnabled() method (i.e. model uses the trait).
  • When passcode is enabled: updates last_activity, sets locked_at and redirects on inactivity ≥ timeout.
  • Allowed routes when locked — from allowed_route_names_when_locked.

5. Routes

Register package routes (e.g. in routes/web.php):

use Sashalenz\PasscodeLock\Http\Controllers\LockController;

// Do not protect with auth so unlock works without re-login
$router->get('lock', [LockController::class, 'show'])->name('lock.show');
$router->post('lock', [LockController::class, 'unlock'])->name('lock.unlock');

$router->get('lock/trigger', [LockController::class, 'trigger'])
    ->middleware('auth:admin') // or your guard
    ->name('lock.trigger');
  • lock.show — PIN entry screen.
  • lock.unlock — POST with code field (4 digits).
  • lock.trigger — instant lock (for idle script and hotkey); must be auth-protected.

6. Lock screen view (Tailwind + Vite)

The package provides view passcode-lock::lock with Tailwind CSS and Vite layout. No color hardcoding — uses Tailwind utilities (primary-*, gray-*), overridable via @theme.

Tailwind content — add package views in resources/css/app.css:

@source "../../packages/sashalenz/passcode-lock/resources/views/**/*.blade.php";
@source "../../vendor/sashalenz/passcode-lock/resources/views/**/*.blade.php";

Config options:

Key Description
view View name (default passcode-lock::lock).
layout Layout for lock view (default passcode-lock::layouts.lock). Must load Vite and Tailwind.
vite_entry Vite entry points (default ['resources/css/app.css', 'resources/js/app.js']).
page_title, app_name Page title and app name.
logo_url, logo_alt Logo.
custom_css_url URL for additional CSS.

Theme — prefers-color-scheme and localStorage.theme.

7. Profile passcode settings (Livewire + Wireforms)

The package provides a Livewire component with Wireforms fields (checkbox, text, button). Add it in your profile card:

<x-wiretables::card>
    <x-slot name="header">{{ __('passcode-lock::passcode.section_title') }}</x-slot>
    <div class="p-2">
        <livewire:passcode-lock.passcode-settings guard="admin" />
    </div>
</x-wiretables::card>

Layout must include <x-wireforms::notification/> for success/error messages.

8. Idle and hotkey (Alpine.js)

In the layout where auto-lock and Ctrl+Alt+L manual lock are needed:

@include('passcode-lock::idle-script')

The partial registers Alpine component passcodeIdle and renders it only for users with passcode enabled. Config idle_check_interval_ms (env: PASSCODE_LOCK_IDLE_CHECK_MS) — check interval in ms (default 30000).

9. Custom passcode form (without package component)

If not using the package component, call trait methods in your own form:

  • Enabled: hasPasscodeEnabled(), disable — setPasscode(null).
  • Timeout: setPasscode($code, $timeoutMinutes) or updatePasscodeTimeout($timeoutMinutes).
  • New PIN: validate digits:4, confirmed; save — setPasscode($plainCode, $timeoutMinutes).

Translations

The package provides translations in resources/lang/{en,uk}/auth.php and passcode.php (namespace passcode-lock::auth, passcode-lock::passcode). Keys: locked, application_locked, enter_passcode, code, logout, unlock, invalid_passcode, and passcode form labels.

Publish (optional):

php artisan vendor:publish --tag=passcode-lock-translations

Migrating from columns to passcodes table

If passcode was stored in model columns (passcode_enabled, passcode_code, passcode_timeout_minutes), migrate data to the passcodes table and drop those columns. Example migration — in the app repository using this package (e.g. migrate_passcodes_to_package_and_drop_columns).

License

MIT.