Compare commits

..

No commits in common. "3c8d60e61471b51f6bb4d17a251162cf04423b1e" and "4cc5279842be8af57e406b8a1bc798975ca94981" have entirely different histories.

2 changed files with 18 additions and 23 deletions

View file

@ -14,24 +14,26 @@
// stores and forwards it as an opaque blob and never holds the key — see
// `pb_hooks/device_page.pb.js` and the app's `device_key_transfer.dart`.
// NOTE: PocketBase runs every routerAdd/cronAdd handler in its OWN isolated JSVM,
// so file-level consts/functions are NOT visible inside the callbacks — referencing
// them throws `ReferenceError: x is not defined` at request time. So everything a
// handler needs (nowSeconds, the code alphabet, the TTLs) is defined *inside* it.
// Keep it that way; don't hoist helpers back to file scope.
const DEVICE_TTL_SECONDS = 300 // 5 min to approve a fresh request
const PICKUP_TTL_SECONDS = 120 // after approval, the TV has this long to fetch
// Human code: 8 chars, unambiguous alphabet (Crockford minus I L O U), shown as
// XXXX-XXXX. Stored without the dash; the app + page normalize before lookup.
const CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
function nowSeconds() {
return Math.floor(Date.now() / 1000)
}
function genCode() {
return $security.randomStringWithAlphabet(8, CODE_ALPHABET)
}
// ── POST /api/device-auth/request ─────────────────────────────────────────────
// Public. The TV creates a sign-in request and gets back the code to show, the
// pollSecret only it will hold, and the id + expiry. Body: { deviceName,
// devicePubKey }.
routerAdd("POST", "/api/device-auth/request", (e) => {
const nowSeconds = () => Math.floor(Date.now() / 1000)
const DEVICE_TTL_SECONDS = 300 // 5 min to approve a fresh request
// Human code: 8 chars, unambiguous alphabet (Crockford minus I L O U), shown as
// XXXX-XXXX. Stored without the dash; the app + page normalize before lookup.
const CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
const genCode = () => $security.randomStringWithAlphabet(8, CODE_ALPHABET)
const data = new DynamicModel({ deviceName: "", devicePubKey: "" })
e.bindBody(data)
@ -75,7 +77,6 @@ routerAdd("POST", "/api/device-auth/request", (e) => {
// and to read the TV's ephemeral public key it encrypts the vault key to. Returns
// nothing sensitive (no pollSecret, no token).
routerAdd("GET", "/api/device-auth/info", (e) => {
const nowSeconds = () => Math.floor(Date.now() / 1000)
const code = (e.request.url.query().get("code") || "").toUpperCase().trim()
if (!code) throw new BadRequestError("missing code")
@ -102,7 +103,6 @@ routerAdd("GET", "/api/device-auth/info", (e) => {
// The minted token + the E2E vault-key ciphertext are only ever returned here, to
// a caller holding the matching pollSecret.
routerAdd("GET", "/api/device-auth/poll", (e) => {
const nowSeconds = () => Math.floor(Date.now() / 1000)
const id = e.request.url.query().get("id") || ""
const secret = e.request.url.query().get("secret") || ""
if (!id || !secret) throw new BadRequestError("missing id/secret")
@ -169,8 +169,6 @@ routerAdd("GET", "/api/device-auth/poll", (e) => {
// token for the caller and stores the E2E vault-key ciphertext the page produced.
// Body: { code, keyCiphertext }.
routerAdd("POST", "/api/device-auth/approve", (e) => {
const nowSeconds = () => Math.floor(Date.now() / 1000)
const PICKUP_TTL_SECONDS = 120 // after approval, the TV has this long to fetch
const data = new DynamicModel({ code: "", keyCiphertext: "" })
e.bindBody(data)
const code = (data.code || "").toUpperCase().trim()
@ -235,7 +233,6 @@ routerAdd("POST", "/api/device-auth/decline", (e) => {
// pending/denied rows. The device list keeps the approved rows (status + user +
// created), just without any live secret at rest.
cronAdd("device_auth_cleanup", "*/5 * * * *", () => {
const nowSeconds = () => Math.floor(Date.now() / 1000)
const now = nowSeconds()
try {
const stale = $app.findRecordsByFilter(

View file

@ -20,10 +20,10 @@
// reaches the server (E2E for this one blob). The minted token is delivered by the
// poll route, not here.
// NOTE: PocketBase runs this route handler in its own isolated JSVM — a file-level
// `const DEVICE_APPROVE_HTML` is invisible inside it (ReferenceError at request
// time). So the whole self-contained page is defined *inside* the handler.
routerAdd("GET", "/device", (e) => {
return e.html(200, DEVICE_APPROVE_HTML)
})
const DEVICE_APPROVE_HTML = `<!doctype html>
<html lang="cs">
<head>
@ -277,5 +277,3 @@ loadInfo();
</script>
</body>
</html>`
return e.html(200, DEVICE_APPROVE_HTML)
})