From b27cde13d0058ff4afe458f585648801637108a3 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Sat, 22 Nov 2025 00:46:55 +0000 Subject: [PATCH] Fix unlock detection to work with server-side data filtering - Update unlock-system.js to check 'locked' field instead of 'requires' for lock detection - Pass null for key/pin/password required values (server validates) - Preserve 'requires' field for biometric/bluetooth locks (contains item identifiers, not answers) - Update both Game model and controller filtering methods Fixes issue where locked objects didn't prompt for unlock after server-side filtering was implemented. --- .../break_escape/games_controller.rb | 8 +++-- app/models/break_escape/game.rb | 8 +++-- .../break_escape/js/systems/unlock-system.js | 29 +++++++++++-------- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/controllers/break_escape/games_controller.rb b/app/controllers/break_escape/games_controller.rb index db86f31..2e3096b 100644 --- a/app/controllers/break_escape/games_controller.rb +++ b/app/controllers/break_escape/games_controller.rb @@ -304,8 +304,12 @@ module BreakEscape def filter_requires_recursive(obj) case obj when Hash - # Remove 'requires' (the answer/solution) from all objects - obj.delete('requires') + # Remove 'requires' for exploitable lock types (key/pin/password/rfid) + # Keep it for biometric/bluetooth since they reference collectible items, not answers + lock_type = obj['lockType'] + if lock_type && !%w[biometric bluetooth].include?(lock_type) + obj.delete('requires') + end # Recursively filter nested structures obj.each_value { |value| filter_requires_recursive(value) } diff --git a/app/models/break_escape/game.rb b/app/models/break_escape/game.rb index 4c7e6a8..13b9e3f 100644 --- a/app/models/break_escape/game.rb +++ b/app/models/break_escape/game.rb @@ -178,8 +178,12 @@ module BreakEscape def filter_requires_and_contents_recursive(obj) case obj when Hash - # Remove 'requires' (the answer/solution) - obj.delete('requires') + # Remove 'requires' for exploitable lock types (key/pin/password/rfid) + # Keep it for biometric/bluetooth since they reference collectible items, not answers + lock_type = obj['lockType'] + if lock_type && !%w[biometric bluetooth].include?(lock_type) + obj.delete('requires') + end # Remove 'contents' if locked (lazy-loaded via separate endpoint) obj.delete('contents') if obj['locked'] diff --git a/public/break_escape/js/systems/unlock-system.js b/public/break_escape/js/systems/unlock-system.js index 00ba13b..b7a34ef 100644 --- a/public/break_escape/js/systems/unlock-system.js +++ b/public/break_escape/js/systems/unlock-system.js @@ -41,10 +41,11 @@ export function handleUnlock(lockable, type) { console.log('NO LOCK REQUIREMENTS FOUND'); return; } - + // Check if object is locked based on lock requirements - const isLocked = lockRequirements.requires; - + // Use 'locked' field instead of 'requires' (which is filtered server-side for security) + const isLocked = lockRequirements.locked !== false; + if (!isLocked) { console.log('OBJECT NOT LOCKED'); return; @@ -63,9 +64,11 @@ export function handleUnlock(lockable, type) { switch(lockRequirements.lockType) { case 'key': - const requiredKey = lockRequirements.requires; - console.log('KEY REQUIRED', requiredKey); - + // Note: requiredKey no longer available from server (security filtered) + // Server will validate on unlock attempt + const requiredKey = null; // Will be validated server-side + console.log('KEY REQUIRED (server-side validation)'); + // Get all keys from player's inventory (including key ring) let playerKeys = []; @@ -171,8 +174,9 @@ export function handleUnlock(lockable, type) { break; case 'pin': - console.log('PIN CODE REQUESTED'); - startPinMinigame(lockable, type, lockRequirements.requires, (success) => { + console.log('PIN CODE REQUESTED (server-side validation)'); + // Pass null for required code - will be validated server-side + startPinMinigame(lockable, type, null, (success) => { if (success) { unlockTarget(lockable, type, lockable.layer); } @@ -180,8 +184,8 @@ export function handleUnlock(lockable, type) { break; case 'password': - console.log('PASSWORD REQUESTED'); - + console.log('PASSWORD REQUESTED (server-side validation)'); + // Get password options from the lockable object const passwordOptions = { passwordHint: lockable.passwordHint || lockable.scenarioData?.passwordHint || '', @@ -191,8 +195,9 @@ export function handleUnlock(lockable, type) { postitNote: lockable.postitNote || lockable.scenarioData?.postitNote || '', showPostit: lockable.showPostit || lockable.scenarioData?.showPostit || false }; - - startPasswordMinigame(lockable, type, lockRequirements.requires, (success) => { + + // Pass null for required password - will be validated server-side + startPasswordMinigame(lockable, type, null, (success) => { if (success) { unlockTarget(lockable, type, lockable.layer); }