Add backend PIN verification and security hardening
- Add requirePin() check on add/update/delete endpoints (closes PIN bypass vulnerability) - Restrict CORS to specific allowed origins only - Add input length limits to sanitize() function - Frontend now sends currentPin with all write requests - Deploy script copies data/index.php to block directory listing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
60
api.php
60
api.php
@@ -1,13 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* Pantry Inventory API
|
||||
*
|
||||
*
|
||||
* Simple JSON file-based backend for the pantry inventory app.
|
||||
* No database required - stores everything in inventory.json
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// CORS - restrict to same origin (remove for local development if needed)
|
||||
$allowedOrigins = [
|
||||
'https://pantry.kestrelsnest.social',
|
||||
'http://localhost:8000'
|
||||
];
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
if (in_array($origin, $allowedOrigins)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
}
|
||||
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
@@ -135,8 +144,26 @@ function getNextId($inventory) {
|
||||
/**
|
||||
* Sanitize string input
|
||||
*/
|
||||
function sanitize($str) {
|
||||
return trim($str);
|
||||
function sanitize($str, $maxLength = 200) {
|
||||
$str = trim($str);
|
||||
if (strlen($str) > $maxLength) {
|
||||
$str = substr($str, 0, $maxLength);
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if PIN is required and valid for write operations
|
||||
*/
|
||||
function requirePin($input) {
|
||||
// If no PIN is set yet, allow writes (first-time setup)
|
||||
if (!hasPinSet()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// PIN is set, so require it for write operations
|
||||
$pin = $input['pin'] ?? '';
|
||||
return verifyPin($pin);
|
||||
}
|
||||
|
||||
// Main request handling
|
||||
@@ -195,10 +222,15 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'add':
|
||||
if (!requirePin($input)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid PIN']);
|
||||
break;
|
||||
}
|
||||
|
||||
$name = sanitize($input['name'] ?? '');
|
||||
$container = sanitize($input['container'] ?? 'glass-bail-medium');
|
||||
$container = sanitize($input['container'] ?? 'glass-bail-medium', 50);
|
||||
$outOfStock = (bool)($input['outOfStock'] ?? false);
|
||||
|
||||
|
||||
if (empty($name)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Name required']);
|
||||
break;
|
||||
@@ -219,11 +251,16 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
if (!requirePin($input)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid PIN']);
|
||||
break;
|
||||
}
|
||||
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
$name = sanitize($input['name'] ?? '');
|
||||
$container = sanitize($input['container'] ?? '');
|
||||
$container = sanitize($input['container'] ?? '', 50);
|
||||
$outOfStock = (bool)($input['outOfStock'] ?? false);
|
||||
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid ID']);
|
||||
break;
|
||||
@@ -252,8 +289,13 @@ switch ($action) {
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (!requirePin($input)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid PIN']);
|
||||
break;
|
||||
}
|
||||
|
||||
$id = (int)($input['id'] ?? 0);
|
||||
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid ID']);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user