Add NFC chore submission feature and enhance family settings
- Introduced NFC support for chore submissions, allowing specific person credit after Head of Household approval. - Updated family settings to include NFC base URL, scan cooldown, and confirmation page options. - Enhanced chore management with options for anyone to complete chores and NFC link generation. - Improved API endpoints for handling NFC tokens and chore submissions. - Updated readme to reflect new NFC features and settings.
This commit is contained in:
@@ -4,6 +4,7 @@ require_once __DIR__ . '/persona.php';
|
||||
|
||||
const CHORE_SCHEDULE_ONCE = 'once';
|
||||
const CHORE_SCHEDULE_RECURRING = 'recurring';
|
||||
const CHORE_NFC_HASH_ALGO = 'sha256';
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
@@ -106,9 +107,60 @@ function migrateLegacyChoreRow(array $c, array $people): array {
|
||||
if ($c['pending_submission'] !== null && !is_array($c['pending_submission'])) {
|
||||
$c['pending_submission'] = null;
|
||||
}
|
||||
$c['anyone_can_complete'] = !empty($c['anyone_can_complete']);
|
||||
$c['nfc'] = normalizeChoreNfcMeta($c['nfc'] ?? null);
|
||||
return $c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function normalizeChoreNfcMeta($raw): array {
|
||||
if (!is_array($raw)) {
|
||||
return [
|
||||
'token_hash' => '',
|
||||
'enabled' => false,
|
||||
'created_at' => '',
|
||||
'last_used_at' => null,
|
||||
'last_used_ip' => null,
|
||||
];
|
||||
}
|
||||
$tokenHash = isset($raw['token_hash']) ? trim((string) $raw['token_hash']) : '';
|
||||
$createdAt = isset($raw['created_at']) ? trim((string) $raw['created_at']) : '';
|
||||
$enabled = !empty($raw['enabled']) && $tokenHash !== '';
|
||||
$lastUsedAt = isset($raw['last_used_at']) ? trim((string) $raw['last_used_at']) : '';
|
||||
$lastUsedIp = isset($raw['last_used_ip']) ? trim((string) $raw['last_used_ip']) : '';
|
||||
return [
|
||||
'token_hash' => $tokenHash,
|
||||
'enabled' => $enabled,
|
||||
'created_at' => $createdAt,
|
||||
'last_used_at' => $lastUsedAt !== '' ? $lastUsedAt : null,
|
||||
'last_used_ip' => $lastUsedIp !== '' ? $lastUsedIp : null,
|
||||
];
|
||||
}
|
||||
|
||||
function generateOpaqueToken(int $bytes = 24): string {
|
||||
$raw = random_bytes($bytes);
|
||||
return rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
function hashOpaqueToken(string $token): string {
|
||||
return hash(CHORE_NFC_HASH_ALGO, $token);
|
||||
}
|
||||
|
||||
function validateTokenHash(string $token, string $storedHash): bool {
|
||||
if ($token === '' || $storedHash === '') {
|
||||
return false;
|
||||
}
|
||||
return hash_equals($storedHash, hashOpaqueToken($token));
|
||||
}
|
||||
|
||||
function requestClientAddress(): ?string {
|
||||
$ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
return $ip !== '' ? $ip : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $lists
|
||||
* @return array<int, array{type: string, items: array<int, string>}>
|
||||
|
||||
Reference in New Issue
Block a user