littleshop/docs/CHANNEL_LOCK_DESIGN.md
SysAdmin 7806bb2392 Docs: Channel Lock & PIN Protection feature design
- Created comprehensive design document (docs/CHANNEL_LOCK_DESIGN.md)
- Complete UX flows with PIN setup, unlock, and auto-lock modes
- Technical implementation details with code examples
- Security architecture (PBKDF2, brute force protection)
- Database schema changes and migration plan
- Testing strategy and implementation checklist
- Added to ROADMAP.md as Phase 3, Item #1 (HIGH PRIORITY)
- Target: October 2025 implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-06 04:39:29 +01:00

734 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TeleBot Channel Lock & PIN Protection Design
**Status**: 📋 Documented - Ready for Implementation
**Priority**: 🔒 HIGH - Security & Privacy Enhancement
**Target**: Q4 2025 (October 2025)
**Estimated Implementation**: 2-3 hours core functionality + 1-2 hours UX polish
**Last Updated**: October 6, 2025
---
## Executive Summary
Complete channel lock system for TeleBot that provides enterprise-grade privacy protection through a 4-digit PIN. When enabled, the entire Telegram conversation is locked with a single security gate, requiring PIN entry to access any functionality beyond the unlock screen.
### Key Benefits
**Single Security Boundary** - One gate protects all features, not selective locks
**Screenshot Safe** - Locked screen shows no sensitive data
**Shared Device Safe** - Complete protection for shared/public devices
**Mobile Banking UX** - Familiar pattern from banking/crypto wallet apps
**No Data Loss** - All data preserved in session, just visibility controlled
**Privacy by Design** - User controls when and how data is visible
**Simple Implementation** - Early gate check, minimal code complexity
---
## Architecture Overview
### Core Concept
```
┌─────────────────────────────────────────┐
│ ALL TELEGRAM UPDATES │
│ (Messages, Callbacks, Commands) │
└──────────────────┬──────────────────────┘
┌─────────────────────┐
│ SECURITY GATE │
│ Is session locked? │
└─────────┬───────────┘
┌─────────┴──────────┐
│ │
▼ ▼
🔒 LOCKED ✅ UNLOCKED
Show unlock Process normally
screen only (full features)
```
### State Model
```csharp
public enum SessionState
{
Guest, // No PIN set, full access (backward compatible with existing users)
Unlocked, // PIN set, currently unlocked (full access)
Locked // PIN set, currently locked (GATE BLOCKS ALL ACTIONS)
}
```
---
## User Experience Design
### 1. First-Time Setup (Optional)
```
┌─────────────────────────────────────┐
│ 🔐 Secure Your Orders │
├─────────────────────────────────────┤
│ Protect your personal information │
│ and order history with a 4-digit │
│ PIN code. │
│ │
│ • Lock chat automatically │
│ • Protect payment details │
│ • Secure order history │
│ │
│ [✅ Set Up PIN] [⏭️ Skip] │
└─────────────────────────────────────┘
```
**Design Decisions:**
- Completely optional - existing users continue with Guest mode
- Clear value proposition - explain what PIN protects
- No friction - skip option for users who don't need it
- Re-prompt after 3 orders or 7 days (gentle nudge)
### 2. PIN Setup Flow
```
Step 1: Enter New PIN
┌─────────────────────────────────────┐
│ 🔢 Create Your PIN │
├─────────────────────────────────────┤
│ PIN: ● ● ● ● │
│ │
│ [1] [2] [3] │
│ [4] [5] [6] │
│ [7] [8] [9] │
│ [⬅️ Clear] [0] [✅ Next] │
└─────────────────────────────────────┘
Step 2: Confirm PIN
┌─────────────────────────────────────┐
│ 🔢 Confirm Your PIN │
├─────────────────────────────────────┤
│ Enter your PIN again to confirm │
│ │
│ PIN: ● ● ● ● │
│ │
│ [Numeric keypad as above] │
└─────────────────────────────────────┘
Step 3: Success
┌─────────────────────────────────────┐
│ ✅ PIN Protection Enabled │
├─────────────────────────────────────┤
│ Your chat is now protected with │
│ a secure PIN. │
│ │
│ Auto-lock: 🏠 On Main Menu │
│ │
│ [⚙️ Settings] [🏠 Main Menu] │
└─────────────────────────────────────┘
```
### 3. Locked State (Primary UX)
```
┌─────────────────────────────────────┐
│ 🔒 Channel Locked │
├─────────────────────────────────────┤
│ Enter your 4-digit PIN to continue │
│ │
│ Locked at: 14:32 │
│ │
│ [🔓 Unlock] │
└─────────────────────────────────────┘
```
**Important:** This is the ONLY message shown when locked. No other features accessible.
### 4. Unlock Flow
```
┌─────────────────────────────────────┐
│ 🔢 Enter PIN │
├─────────────────────────────────────┤
│ PIN: ● ● ○ ○ │
│ │
│ Attempts remaining: 3 │
│ │
│ [1] [2] [3] │
│ [4] [5] [6] │
│ [7] [8] [9] │
│ [⬅️ Clear] [0] [✅ Submit] │
└─────────────────────────────────────┘
After Successful Unlock:
┌─────────────────────────────────────┐
│ ✅ Unlocked │
├─────────────────────────────────────┤
│ Welcome back! │
│ │
│ [Returns to normal menu] │
└─────────────────────────────────────┘
Failed Attempt:
┌─────────────────────────────────────┐
│ ❌ Incorrect PIN │
├─────────────────────────────────────┤
│ PIN: ○ ○ ○ ○ │
│ │
│ Attempts remaining: 2 │
│ ⚠️ Account locks after 3 failures │
│ │
│ [Numeric keypad] │
└─────────────────────────────────────┘
Account Locked (after 3 failures):
┌─────────────────────────────────────┐
│ 🚫 Account Temporarily Locked │
├─────────────────────────────────────┤
│ Too many failed attempts. │
│ │
│ Try again in: 5 minutes │
│ │
│ [❓ Forgot PIN?] │
└─────────────────────────────────────┘
```
### 5. Auto-Lock Modes
```
⚙️ Security Settings
├── 🔐 PIN Protection: ✅ Enabled
│ ├── 📌 Change PIN
│ └── ❌ Disable PIN (requires current PIN)
├── 🔒 Auto-Lock: 🏠 On Main Menu ▼
│ │
│ ├── ✋ Manual Only
│ │ └── Lock only when "🔒 Lock" button clicked
│ │
│ ├── 🏠 On Main Menu (Recommended)
│ │ └── Auto-lock when returning to main menu
│ │
│ ├── ⏰ After Inactivity
│ │ └── Lock after [30 ▼] minutes idle
│ │
│ ├── 🚪 On Exit
│ │ └── Lock on any "back to menu" action
│ │
│ └── 🔐 Always (Paranoid Mode)
│ └── Lock after EVERY single action
├── 🗑️ Clear All Data (requires PIN)
│ └── Permanently delete order history, cart, settings
└── Security Info
├── PIN Last Changed: Oct 6, 2025
├── Last Locked: 2 hours ago
└── Failed Attempts Today: 0
```
---
## Technical Implementation
### 1. Database Schema Changes
```csharp
public class UserSession
{
// Existing fields...
public Guid Id { get; set; }
public long TelegramUserId { get; set; }
public Cart Cart { get; set; }
public List<ConversationMessage> Conversation { get; set; }
// NEW: Security & Lock State
public SessionState State { get; set; } = SessionState.Guest;
public string? PinHash { get; set; } // PBKDF2 hash, null = no PIN
public DateTime? LastActivityAt { get; set; }
public DateTime? LockedAt { get; set; }
public int FailedPinAttempts { get; set; }
public DateTime? LockoutUntil { get; set; }
public SecuritySettings Security { get; set; } = new();
public string? PinEntry { get; set; } // Temporary, in-memory only (not persisted)
}
public class SecuritySettings
{
public bool PinEnabled { get; set; }
public AutoLockMode AutoLock { get; set; } = AutoLockMode.OnMainMenu;
public int AutoLockMinutes { get; set; } = 30;
public DateTime? PinCreatedAt { get; set; }
public DateTime? PinLastChangedAt { get; set; }
}
public enum AutoLockMode
{
Manual, // Only lock when user clicks button
OnMainMenu, // Lock when returning to main menu (default)
OnInactivity, // Lock after X minutes idle
OnExit, // Lock on any "back to menu" action
Always // Lock after every single action (paranoid mode)
}
```
### 2. Security Gate Implementation
```csharp
// CallbackHandler.cs - Primary entry point for all user interactions
public async Task Handle(ITelegramBotClient bot, CallbackQuery callbackQuery, CancellationToken ct)
{
var telegramUser = callbackQuery.From;
var session = await _sessionManager.GetOrCreateSessionAsync(telegramUser.Id);
// UPDATE ACTIVITY TIMESTAMP
session.LastActivityAt = DateTime.UtcNow;
// SECURITY GATE - Check lock state BEFORE any other processing
if (session.State == SessionState.Locked && !IsUnlockAction(callbackQuery.Data))
{
await ShowUnlockScreen(bot, callbackQuery);
return; // STOP ALL PROCESSING - gate closed
}
// Check if account is in lockout period
if (session.LockoutUntil.HasValue && DateTime.UtcNow < session.LockoutUntil.Value)
{
await ShowLockoutScreen(bot, callbackQuery, session.LockoutUntil.Value);
return;
}
// If unlocked or this is unlock action, continue normal flow
await ProcessCallback(bot, callbackQuery, session);
// CHECK AUTO-LOCK CONDITIONS after action completes
await CheckAndApplyAutoLock(session);
}
private bool IsUnlockAction(string? callbackData)
{
return callbackData?.StartsWith("unlock") == true ||
callbackData?.StartsWith("pin_entry:") == true ||
callbackData == "show_pin_pad" ||
callbackData == "pin_clear" ||
callbackData?.StartsWith("pin_digit:") == true;
}
```
### 3. PIN Management
```csharp
// PIN Hashing (secure storage)
public string HashPin(string pin)
{
using var rfc2898 = new Rfc2898DeriveBytes(
pin,
saltSize: 32,
iterations: 100_000,
HashAlgorithmName.SHA256
);
var hash = Convert.ToBase64String(rfc2898.GetBytes(32));
var salt = Convert.ToBase64String(rfc2898.Salt);
return $"{salt}:{hash}"; // Store both salt and hash
}
// PIN Verification
public bool VerifyPin(string enteredPin, string storedHash)
{
var parts = storedHash.Split(':');
if (parts.Length != 2) return false;
var salt = Convert.FromBase64String(parts[0]);
var hash = Convert.FromBase64String(parts[1]);
using var rfc2898 = new Rfc2898DeriveBytes(
enteredPin,
salt,
iterations: 100_000,
HashAlgorithmName.SHA256
);
var enteredHash = rfc2898.GetBytes(32);
return CryptographicOperations.FixedTimeEquals(hash, enteredHash);
}
// PIN Entry Handling
private async Task HandlePinDigit(ITelegramBotClient bot, CallbackQuery query, UserSession session, string digit)
{
if (session.PinEntry == null)
session.PinEntry = "";
if (digit == "clear")
{
session.PinEntry = "";
}
else if (digit == "submit" && session.PinEntry.Length == 4)
{
await HandlePinSubmit(bot, query, session);
return;
}
else if (session.PinEntry.Length < 4)
{
session.PinEntry += digit;
}
// Refresh PIN pad with updated dots
await ShowPinPad(bot, query, session);
}
private async Task HandlePinSubmit(ITelegramBotClient bot, CallbackQuery query, UserSession session)
{
if (VerifyPin(session.PinEntry, session.PinHash!))
{
// SUCCESS
session.State = SessionState.Unlocked;
session.FailedPinAttempts = 0;
session.PinEntry = null;
await bot.EditMessageTextAsync(
query.Message!.Chat.Id,
query.Message.MessageId,
"✅ *Unlocked*\n\nWelcome back!",
parseMode: ParseMode.Markdown,
replyMarkup: MenuBuilder.MainMenu()
);
}
else
{
// FAILURE
session.FailedPinAttempts++;
session.PinEntry = null;
if (session.FailedPinAttempts >= 3)
{
// LOCKOUT
session.LockoutUntil = DateTime.UtcNow.AddMinutes(5);
await ShowLockoutScreen(bot, query, session.LockoutUntil.Value);
}
else
{
await ShowPinPad(bot, query, session, isError: true);
}
}
await _sessionManager.SaveSessionAsync(session);
}
```
### 4. Auto-Lock Logic
```csharp
private async Task CheckAndApplyAutoLock(UserSession session)
{
// Don't lock if PIN not enabled or already locked
if (!session.Security.PinEnabled || session.State == SessionState.Locked)
return;
bool shouldLock = session.Security.AutoLock switch
{
AutoLockMode.Manual => false, // Never auto-lock
AutoLockMode.OnMainMenu =>
session.CurrentMenu == "main" || session.LastAction == "menu",
AutoLockMode.OnInactivity =>
(DateTime.UtcNow - session.LastActivityAt.GetValueOrDefault()).TotalMinutes
>= session.Security.AutoLockMinutes,
AutoLockMode.OnExit =>
session.CurrentMenu == "main" ||
session.LastAction == "back" ||
session.LastAction == "menu",
AutoLockMode.Always => true, // Lock after every action
_ => false
};
if (shouldLock)
{
session.State = SessionState.Locked;
session.LockedAt = DateTime.UtcNow;
session.PinEntry = null; // Clear any temporary PIN entry
await _sessionManager.SaveSessionAsync(session);
_logger.LogInformation(
"Session {SessionId} auto-locked (mode: {Mode})",
session.Id,
session.Security.AutoLock
);
}
}
```
### 5. UI Components
```csharp
// MenuBuilder.cs additions
public InlineKeyboardMarkup UnlockScreen()
{
return new InlineKeyboardMarkup(new[]
{
new[]
{
InlineKeyboardButton.WithCallbackData("🔓 Unlock", "show_pin_pad")
}
});
}
public InlineKeyboardMarkup PinPad(UserSession session, bool isError = false)
{
var maskLength = session.PinEntry?.Length ?? 0;
var mask = new string('●', maskLength) + new string('○', 4 - maskLength);
return new InlineKeyboardMarkup(new[]
{
new[]
{
InlineKeyboardButton.WithCallbackData("1", "pin_digit:1"),
InlineKeyboardButton.WithCallbackData("2", "pin_digit:2"),
InlineKeyboardButton.WithCallbackData("3", "pin_digit:3")
},
new[]
{
InlineKeyboardButton.WithCallbackData("4", "pin_digit:4"),
InlineKeyboardButton.WithCallbackData("5", "pin_digit:5"),
InlineKeyboardButton.WithCallbackData("6", "pin_digit:6")
},
new[]
{
InlineKeyboardButton.WithCallbackData("7", "pin_digit:7"),
InlineKeyboardButton.WithCallbackData("8", "pin_digit:8"),
InlineKeyboardButton.WithCallbackData("9", "pin_digit:9")
},
new[]
{
InlineKeyboardButton.WithCallbackData("⬅️ Clear", "pin_clear"),
InlineKeyboardButton.WithCallbackData("0", "pin_digit:0"),
InlineKeyboardButton.WithCallbackData("✅ Submit", "pin_submit")
}
});
}
// Add lock button to main menu when PIN enabled
public InlineKeyboardMarkup MainMenu(UserSession? session = null)
{
var buttons = new List<InlineKeyboardButton[]>
{
// ... existing menu items
};
// Add lock button if PIN is enabled
if (session?.Security.PinEnabled == true && session.State == SessionState.Unlocked)
{
buttons.Add(new[]
{
InlineKeyboardButton.WithCallbackData("🔒 Lock Chat", "lock_channel")
});
}
return new InlineKeyboardMarkup(buttons);
}
```
---
## Security Considerations
### 1. PIN Storage
- ✅ PBKDF2 with 100,000 iterations (OWASP recommended)
- ✅ 32-byte random salt per PIN
- ✅ SHA-256 hash algorithm
- ✅ Constant-time comparison (prevents timing attacks)
-**Never** store PIN in plaintext
### 2. Brute Force Protection
- ✅ 3 attempts before 5-minute lockout
- ✅ Progressive lockout (future: 5min → 15min → 1hr → 24hr)
- ✅ Failed attempt logging for audit trail
- ✅ Rate limiting on unlock endpoint
### 3. Session Security
- ✅ PinEntry stored in memory only, never persisted to database
- ✅ Auto-clear PinEntry on lock/unlock/failure
- ✅ Session state synchronized across all interactions
- ✅ Activity timestamp updated on every interaction
### 4. Data Protection
- ✅ All sensitive data (orders, payments, addresses) hidden when locked
- ✅ No data exposed in locked state messages
- ✅ Session data preserved but inaccessible
- ✅ Optional "Clear All Data" requires PIN verification
---
## Migration & Backward Compatibility
### Existing Users (Guest Mode)
- No disruption - continue using bot without PIN
- Optional prompt after 3 orders or 7 days: "Would you like to secure your orders with a PIN?"
- Can enable PIN anytime from Settings menu
### Database Migration
```sql
-- Add new columns to UserSessions table
ALTER TABLE UserSessions ADD COLUMN State INTEGER DEFAULT 0; -- 0 = Guest
ALTER TABLE UserSessions ADD COLUMN PinHash TEXT NULL;
ALTER TABLE UserSessions ADD COLUMN LastActivityAt TEXT NULL;
ALTER TABLE UserSessions ADD COLUMN LockedAt TEXT NULL;
ALTER TABLE UserSessions ADD COLUMN FailedPinAttempts INTEGER DEFAULT 0;
ALTER TABLE UserSessions ADD COLUMN LockoutUntil TEXT NULL;
ALTER TABLE UserSessions ADD COLUMN SecuritySettings TEXT NULL; -- JSON
-- Add indexes for performance
CREATE INDEX IX_UserSessions_State ON UserSessions(State);
CREATE INDEX IX_UserSessions_LockoutUntil ON UserSessions(LockoutUntil);
```
---
## Testing Strategy
### Unit Tests
- ✅ PIN hashing and verification
- ✅ Auto-lock mode logic
- ✅ Lockout calculation
- ✅ PIN entry validation (4 digits only)
### Integration Tests
- ✅ Full unlock flow (locked → pin entry → unlocked)
- ✅ Failed attempt lockout enforcement
- ✅ Auto-lock triggers (menu, inactivity, exit)
- ✅ State transitions (Guest → Unlocked → Locked)
### E2E Tests (Playwright)
- ✅ First-time PIN setup flow
- ✅ Lock and unlock via menu
- ✅ Auto-lock on main menu return
- ✅ Failed attempts and lockout
- ✅ PIN change workflow
- ✅ Disable PIN and return to Guest mode
---
## Implementation Checklist
### Phase 1: Core Functionality (2-3 hours)
- [ ] Add `SessionState`, `PinHash`, security fields to `UserSession` model
- [ ] Create database migration for new columns
- [ ] Implement `HashPin()` and `VerifyPin()` methods
- [ ] Add security gate at top of `CallbackHandler.Handle()`
- [ ] Create `ShowUnlockScreen()` method
- [ ] Build PIN pad UI in `MenuBuilder.PinPad()`
- [ ] Implement `HandlePinDigit()` and `HandlePinSubmit()`
- [ ] Add lockout logic (3 attempts → 5 min timeout)
- [ ] Implement manual lock button in main menu
### Phase 2: Auto-Lock (1 hour)
- [ ] Create `CheckAndApplyAutoLock()` method
- [ ] Implement all 5 auto-lock modes
- [ ] Add activity timestamp tracking
- [ ] Test auto-lock triggers
### Phase 3: Settings & Management (1 hour)
- [ ] Create Security Settings menu
- [ ] Implement PIN setup flow (new users)
- [ ] Implement Change PIN flow
- [ ] Implement Disable PIN flow (requires current PIN)
- [ ] Add auto-lock mode selector
- [ ] Add "Clear All Data" with PIN verification
### Phase 4: UX Polish (1-2 hours)
- [ ] Add first-time setup prompt (optional, after 3 orders)
- [ ] Improve error messages and feedback
- [ ] Add security info display (last locked, failed attempts)
- [ ] Add countdown timer for lockout screen
- [ ] Implement "Forgot PIN?" recovery flow (contact support)
- [ ] Add haptic/visual feedback for PIN entry
### Phase 5: Testing & Deployment (1 hour)
- [ ] Write unit tests for PIN security
- [ ] Write integration tests for lock flows
- [ ] Manual E2E testing on Telegram
- [ ] Security review (penetration testing)
- [ ] Deploy to staging environment
- [ ] User acceptance testing
- [ ] Deploy to production with feature flag
---
## Future Enhancements
### Short Term (Q1 2026)
- **Biometric Unlock**: Support Telegram's WebApp biometric API
- **PIN Complexity**: Option for 6-digit PIN or alphanumeric password
- **Session Management**: "Lock all devices" from any session
- **Audit Log**: View all unlock events and failed attempts
### Medium Term (Q2 2026)
- **2FA Support**: Optional TOTP/authenticator app second factor
- **Trusted Devices**: Remember devices, require PIN only on new devices
- **Emergency Contacts**: Designate trusted contact for PIN recovery
- **Secure Backup**: Encrypted backup of order history with recovery phrase
### Long Term (Q3-Q4 2026)
- **Hardware Security**: Support for hardware security keys (YubiKey, etc.)
- **Zero-Knowledge Encryption**: End-to-end encrypted order data
- **Multi-Account**: Separate PINs for business vs personal shopping
- **Compliance**: FIPS 140-2 certification for payment card industry
---
## Success Metrics
### Security Metrics
- ✅ Zero unauthorized access incidents
-< 0.1% lockout rate (balance security vs UX)
- 100% PIN hash security (PBKDF2 with strong parameters)
### User Adoption
- 🎯 Target: 60% of active users enable PIN within 30 days
- 🎯 Target: < 5% disable PIN after enabling
- 🎯 Target: > 90% user satisfaction with lock/unlock UX
### Performance
-< 100ms PIN verification time
- < 500ms lock/unlock state transition
- Zero impact on unlocked session performance
---
## Open Questions
1. **Forgot PIN Flow**: Contact support vs security questions vs recovery phrase?
- **Recommendation**: Contact support with identity verification (Telegram username + recent order ID)
2. **PIN Complexity**: Force 4 digits or allow stronger PINs?
- **Recommendation**: Default 4 digits, optional 6-digit or alphanumeric in settings
3. **Session Expiry**: Should locked sessions expire and clear data after X days?
- **Recommendation**: 90-day expiry with warning email/notification at 80 days
4. **Multi-Device**: If user has bot open on phone and desktop, lock both?
- **Recommendation**: Yes, state is server-side, lock applies to all clients
5. **PIN Recovery**: Allow self-service PIN reset via email?
- **Recommendation**: Phase 1 = support only, Phase 2 = email with order verification
---
## References
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
- [Telegram Bot API Security Best Practices](https://core.telegram.org/bots/security)
- [NIST Digital Identity Guidelines](https://pages.nist.gov/800-63-3/)
- [PBKDF2 Implementation Guide](https://cryptobook.nakov.com/mac-and-key-derivation/pbkdf2)
---
**Document Version**: 1.0
**Author**: SilverLabs Development Team
**Review Date**: October 6, 2025
**Next Review**: November 6, 2025 (post-implementation)