From dbf382afebdb7a50105cffe01654743b26ba3b13 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 23:33:00 +0000 Subject: [PATCH] cyberpunkunicorn-os: accept m=nfc on placard links, 0.1.1 frequency-web writes Space placards as /n/?s=v1...&m=nfc so Scan stats count placard taps, and checkUrl refused any m= on /n/, so every placard write failed. Now m=nfc is allowed on /n/ and added when missing, the same as /q/ (/n reads only s). Any other m= value is still refused. The tag check screen shows the NFC mark for placards too. OS_VERSION 0.1.1 for the next release. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AycPrTRVXqSDZhb2NNfyG6 --- cyberpunkunicorn-os/apps/frequency/BACKEND.md | 6 +++--- cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp | 2 +- cyberpunkunicorn-os/apps/frequency/store.cpp | 8 ++++++-- cyberpunkunicorn-os/apps/frequency/store.h | 4 ++-- cyberpunkunicorn-os/build.sh | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cyberpunkunicorn-os/apps/frequency/BACKEND.md b/cyberpunkunicorn-os/apps/frequency/BACKEND.md index 1129beb..968cd5d 100644 --- a/cyberpunkunicorn-os/apps/frequency/BACKEND.md +++ b/cyberpunkunicorn-os/apps/frequency/BACKEND.md @@ -21,13 +21,13 @@ the link, and frequency-web credits the signed-in member (ledger credit, | Tag | URL written | Where the URL comes from | |---|---|---| -| Space check-in node | `${SITE_URL}/n/?s=v1..` | Space settings → Check-in (copy link). `tag` = base64url(first 16 bytes of HMAC-SHA256(server secret, `node-code:v1::`)), so **only the server can mint it** | -| Event door | `${SITE_URL}/q/<7-char slug>?m=nfc` | the event's door code. The device adds `?m=nfc` if missing. **Never** on `/n/` links | +| Space check-in node | `${SITE_URL}/n/?s=v1..&m=nfc` | Space settings → Check-in (copy link). `tag` = base64url(first 16 bytes of HMAC-SHA256(server secret, `node-code:v1::`)), so **only the server can mint it** | +| Event door | `${SITE_URL}/q/<7-char slug>?m=nfc` | the event's door code. The device adds `?m=nfc` if missing | - One NDEF URI record per tag, NTAG213/215/216 only. A signed node URL is ~100 characters, which fits NTAG213's 144 bytes. - Checks before writing (warn, never block): http(s), `/n/` + UUID + `s=v1..<22 base64url chars>` - with no `m=`, or `/q/` + 7-char slug. + with no `m=` other than `m=nfc` (added if missing; `/n` reads only `s`), or `/q/` + 7-char slug. - Optional **permanent** write-lock (NTAG static + dynamic lock bits), behind two confirmations. - Log of tags written: `/frequency/written.csv` on LittleFS diff --git a/cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp b/cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp index 51b385d..b47b4a3 100644 --- a/cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp +++ b/cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp @@ -165,7 +165,7 @@ void showTag(const CardInfo &c, const String &title, const String &headline, boo cyberLine(14, y += 12, "Points to", u.host, cyberHue(180)); cyberLine(14, y += 12, "Path", u.path, kindColor(u.kind)); if (u.kind == URL_VENUE) cyberLine(14, y += 12, "Signed", "yes (?s=v1...)", cyberHue(120)); - if (u.kind == URL_EVENT) + if (u.kind == URL_EVENT || u.kind == URL_VENUE) cyberLine(14, y += 12, "NFC mark", u.fixed == c.uri ? "?m=nfc ok" : "missing ?m=nfc", u.fixed == c.uri ? cyberHue(120) : cyberHue(30)); if (!u.ok) cyberLine(14, y += 12, "Problem", u.problem, cyberHue(30)); diff --git a/cyberpunkunicorn-os/apps/frequency/store.cpp b/cyberpunkunicorn-os/apps/frequency/store.cpp index c56b4ce..3dfea6d 100644 --- a/cyberpunkunicorn-os/apps/frequency/store.cpp +++ b/cyberpunkunicorn-os/apps/frequency/store.cpp @@ -186,8 +186,12 @@ UrlCheck checkUrl(const String &raw) { String node = u.path.substring(3); if (!isUuid(node)) u.problem = "node id is not a UUID"; else if (!isNodeSig(queryParam(query, "s"))) u.problem = "missing or bad ?s=v1... signature"; - else if (queryParam(query, "m").length()) u.problem = "?m= is for /q/ links only"; - else u.ok = true; + else if (queryParam(query, "m").length() && queryParam(query, "m") != "nfc") u.problem = "?m= on a /n/ link must be nfc"; + else { + u.ok = true; + // Placards carry ?m=nfc too, so the site's Scan stats count them as taps. + if (queryParam(query, "m") != "nfc") u.fixed = url + "&m=nfc"; + } } else if (u.path.startsWith("/q/")) { u.kind = URL_EVENT; String slug = u.path.substring(3); diff --git a/cyberpunkunicorn-os/apps/frequency/store.h b/cyberpunkunicorn-os/apps/frequency/store.h index dbcd5dd..77436f1 100644 --- a/cyberpunkunicorn-os/apps/frequency/store.h +++ b/cyberpunkunicorn-os/apps/frequency/store.h @@ -28,7 +28,7 @@ Settings &settings(); // loaded on first use // ---- URLs ------------------------------------------------------------------- enum UrlKind { URL_EMPTY, - URL_VENUE, // /n/?s=v1.. Space check-in node + URL_VENUE, // /n/?s=v1..[&m=nfc] Space check-in node URL_EVENT, // /q/<7-char slug>[?m=nfc] event door code URL_MEMBER, // /c/ member card (proposed) URL_OTHER, @@ -37,7 +37,7 @@ struct UrlCheck { UrlKind kind = URL_EMPTY; bool ok = false; // well formed for its kind String problem; // why not, for the warning screen - String fixed; // url to actually write (e.g. /q/ gets ?m=nfc) + String fixed; // url to actually write (/n/ and /q/ get m=nfc) String host; // "frequencylocal.com" String path; // "/n/" (no query: the signature stays off logs) String token; // member token for URL_MEMBER diff --git a/cyberpunkunicorn-os/build.sh b/cyberpunkunicorn-os/build.sh index 6cbe8a1..3ea7eea 100755 --- a/cyberpunkunicorn-os/build.sh +++ b/cyberpunkunicorn-os/build.sh @@ -9,7 +9,7 @@ PIO_ENV=CYD-2432S028 PLATFORMIO_VERSION=6.2.0 ESPTOOL_VERSION=5.4.0 OS_NAME="CyberUnicorn 5000" -OS_VERSION=0.1.0 +OS_VERSION=0.1.1 source ./boot.conf for var in BOOT_TITLE BOOT_TAGLINE; do