Add /api/amber/set-pin: hidden pinHash rejects client writes (#13/#14)
Testing verify-pin against the live DB showed the correct PIN failing:
the server never held a hash at all. PocketBase silently drops client
PATCHes to hidden fields, so the app's best-effort `PATCH {pinHash}`
had never landed — the device-local verifier masked it on the PC. The
new owner-scoped route writes the hash from superuser context (which
can set hidden fields); pin "" clears it. Hash shape matches the app
and verify-pin: sha256("<profileId>:<pin>") hex.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e96183bba3
commit
a957cc59f8
1 changed files with 32 additions and 0 deletions
|
|
@ -67,3 +67,35 @@ routerAdd("POST", "/api/amber/verify-pin", (e) => {
|
||||||
}
|
}
|
||||||
return e.json(200, { ok: ok })
|
return e.json(200, { ok: ok })
|
||||||
}, $apis.requireAuth("users"))
|
}, $apis.requireAuth("users"))
|
||||||
|
|
||||||
|
// ── POST /api/amber/set-pin ───────────────────────────────────────────────────
|
||||||
|
// Auth: account owner. Body: { profileId, pin } — pin "" clears. Exists because
|
||||||
|
// `pinHash` is hidden and PocketBase **silently drops client writes to hidden
|
||||||
|
// fields**: the app's old best-effort `PATCH {pinHash}` never landed, so the
|
||||||
|
// server never held a hash and verify-pin had nothing to compare (the PC's
|
||||||
|
// local verifier masked this). Hooks run in superuser context, which can write
|
||||||
|
// hidden fields; the hash shape matches the app + verify-pin:
|
||||||
|
// sha256("<profileId>:<pin>") hex.
|
||||||
|
routerAdd("POST", "/api/amber/set-pin", (e) => {
|
||||||
|
const data = new DynamicModel({ profileId: "", pin: "" })
|
||||||
|
e.bindBody(data)
|
||||||
|
const profileId = (data.profileId || "").trim()
|
||||||
|
const pin = (data.pin || "").trim()
|
||||||
|
if (profileId === "" || (pin !== "" && !/^\d{4}$/.test(pin))) {
|
||||||
|
return e.json(400, { ok: false, error: "profileId and a 4-digit (or empty) pin are required" })
|
||||||
|
}
|
||||||
|
|
||||||
|
let rec = null
|
||||||
|
try {
|
||||||
|
rec = $app.findRecordById("profiles", profileId)
|
||||||
|
} catch (_) {
|
||||||
|
return e.json(404, { ok: false, error: "profile not found" })
|
||||||
|
}
|
||||||
|
if (rec.getString("user") !== e.auth.id) {
|
||||||
|
return e.json(404, { ok: false, error: "profile not found" })
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.set("pinHash", pin === "" ? "" : $security.sha256(profileId + ":" + pin))
|
||||||
|
$app.save(rec)
|
||||||
|
return e.json(200, { ok: true })
|
||||||
|
}, $apis.requireAuth("users"))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue