Feature: PIN security for outgoing wallet payments #2

Open
opened 2026-01-01 15:59:48 +00:00 by padreug · 0 comments
Owner

Summary

Add a PIN requirement for outgoing payments above a user-configurable threshold to prevent unauthorized spending.

User Story

As a user, I want to require a PIN before sending money (especially for larger amounts) so that I can protect my wallet from unauthorized access if someone gains access to my device.

Requirements

PIN Setup

  • User can set a 4-6 digit PIN in wallet settings
  • PIN must be confirmed (enter twice) when setting up
  • User configures threshold (e.g., "require PIN for payments over 1000 sats")
  • Threshold options: Always require, specific amount, or never require

Payment Flow

  • When sending payment >= threshold, show PIN entry dialog
  • User enters PIN to authorize payment
  • Invalid PIN shows error with remaining attempts
  • After 5 failed attempts, lock out for 15 minutes

PIN Management

  • Change PIN (requires current PIN or password)
  • Reset PIN via password verification
  • Remove PIN protection (requires password)

Storage

  • PIN stored server-side in LNbits user extra field
  • PIN is hashed with bcrypt before storage
  • Lockout state persisted to survive page refresh

Technical Design

Data Structure (stored in user.extra.walletSecurity)

interface WalletSecuritySettings {
  pinHash: string | null        // bcrypt hash
  pinSalt: string               // Salt for hashing
  threshold: number             // sats (-1=always, 0=never, >0=threshold)
  failedAttempts: number
  lockedUntil: number | null    // Timestamp if locked
  createdAt: number
  updatedAt: number
}

Files to Create

  • src/modules/wallet/types/security.ts - Type definitions
  • src/modules/wallet/services/PinSecurityService.ts - Core service
  • src/modules/wallet/components/security/PinInput.vue - Reusable PIN input
  • src/modules/wallet/components/security/PinEntryDialog.vue - Payment authorization
  • src/modules/wallet/components/security/PinSetupDialog.vue - Setup wizard
  • src/modules/wallet/components/security/PinResetDialog.vue - Reset flow
  • src/modules/wallet/components/security/SecuritySettings.vue - Settings panel
  • src/modules/wallet/composables/usePinSecurity.ts - Composable wrapper

Files to Modify

  • src/core/di-container.ts - Add PIN_SECURITY_SERVICE token
  • src/modules/base/auth/auth-service.ts - Add verifyPassword method
  • src/modules/wallet/components/SendDialog.vue - Integrate PIN check
  • src/modules/wallet/index.ts - Register service
  • src/locales/en.json - Add translations

Dependencies

npm install bcryptjs
npm install -D @types/bcryptjs

Payment Flow Diagram

User clicks Send Payment
        ↓
[Check: pinService.isPinRequired(amount)]
        ↓
   ┌────┴────┐
   No       Yes
   ↓         ↓
Execute   Show PinEntryDialog
payment         ↓
          User enters PIN
                ↓
          [pinService.verifyPin(pin)]
                ↓
         ┌──────┴──────┐
      Invalid        Valid
         ↓             ↓
   Show error,    Execute payment
   allow retry

Security Considerations

  • bcrypt hashing with cost factor 10
  • 5 failed attempts triggers 15-minute lockout
  • Password required to reset or remove PIN
  • PIN never stored in plain text
  • Server-side storage prevents bypass via local storage manipulation
## Summary Add a PIN requirement for outgoing payments above a user-configurable threshold to prevent unauthorized spending. ## User Story As a user, I want to require a PIN before sending money (especially for larger amounts) so that I can protect my wallet from unauthorized access if someone gains access to my device. ## Requirements ### PIN Setup - User can set a 4-6 digit PIN in wallet settings - PIN must be confirmed (enter twice) when setting up - User configures threshold (e.g., "require PIN for payments over 1000 sats") - Threshold options: Always require, specific amount, or never require ### Payment Flow - When sending payment >= threshold, show PIN entry dialog - User enters PIN to authorize payment - Invalid PIN shows error with remaining attempts - After 5 failed attempts, lock out for 15 minutes ### PIN Management - Change PIN (requires current PIN or password) - Reset PIN via password verification - Remove PIN protection (requires password) ### Storage - PIN stored server-side in LNbits user `extra` field - PIN is hashed with bcrypt before storage - Lockout state persisted to survive page refresh ## Technical Design ### Data Structure (stored in user.extra.walletSecurity) ```typescript interface WalletSecuritySettings { pinHash: string | null // bcrypt hash pinSalt: string // Salt for hashing threshold: number // sats (-1=always, 0=never, >0=threshold) failedAttempts: number lockedUntil: number | null // Timestamp if locked createdAt: number updatedAt: number } ``` ### Files to Create - `src/modules/wallet/types/security.ts` - Type definitions - `src/modules/wallet/services/PinSecurityService.ts` - Core service - `src/modules/wallet/components/security/PinInput.vue` - Reusable PIN input - `src/modules/wallet/components/security/PinEntryDialog.vue` - Payment authorization - `src/modules/wallet/components/security/PinSetupDialog.vue` - Setup wizard - `src/modules/wallet/components/security/PinResetDialog.vue` - Reset flow - `src/modules/wallet/components/security/SecuritySettings.vue` - Settings panel - `src/modules/wallet/composables/usePinSecurity.ts` - Composable wrapper ### Files to Modify - `src/core/di-container.ts` - Add PIN_SECURITY_SERVICE token - `src/modules/base/auth/auth-service.ts` - Add verifyPassword method - `src/modules/wallet/components/SendDialog.vue` - Integrate PIN check - `src/modules/wallet/index.ts` - Register service - `src/locales/en.json` - Add translations ### Dependencies ```bash npm install bcryptjs npm install -D @types/bcryptjs ``` ## Payment Flow Diagram ``` User clicks Send Payment ↓ [Check: pinService.isPinRequired(amount)] ↓ ┌────┴────┐ No Yes ↓ ↓ Execute Show PinEntryDialog payment ↓ User enters PIN ↓ [pinService.verifyPin(pin)] ↓ ┌──────┴──────┐ Invalid Valid ↓ ↓ Show error, Execute payment allow retry ``` ## Security Considerations - bcrypt hashing with cost factor 10 - 5 failed attempts triggers 15-minute lockout - Password required to reset or remove PIN - PIN never stored in plain text - Server-side storage prevents bypass via local storage manipulation
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: aiolabs/webapp#2
No description provided.