64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""Registered devices (shop tablets). Only a registered device may offer PIN sign-in."""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from psycopg.rows import dict_row
|
||
|
|
|
||
|
|
from .sessions import new_token, token_hash
|
||
|
|
|
||
|
|
NAME_MAX = 80
|
||
|
|
|
||
|
|
|
||
|
|
def register(conn: Any, key: bytes, *, name: str, user_id: Any) -> tuple[str, uuid.UUID]:
|
||
|
|
"""Insert a device; returns (cookie token, device id)."""
|
||
|
|
token = new_token()
|
||
|
|
row = conn.execute(
|
||
|
|
"INSERT INTO devices (name, token_hash, registered_by, registered_at, last_seen_at)"
|
||
|
|
" VALUES (%s, %s, %s, now(), now()) RETURNING id",
|
||
|
|
(name, token_hash(key, token), user_id),
|
||
|
|
).fetchone()
|
||
|
|
return token, row[0]
|
||
|
|
|
||
|
|
|
||
|
|
def find(conn: Any, key: bytes, token: str | None) -> dict[str, Any] | None:
|
||
|
|
"""The unrevoked device for a cookie token, or None."""
|
||
|
|
if not token:
|
||
|
|
return None
|
||
|
|
with conn.cursor(row_factory=dict_row) as cur:
|
||
|
|
row: dict[str, Any] | None = cur.execute(
|
||
|
|
"SELECT id, name, registered_at, last_seen_at FROM devices WHERE token_hash = %s AND revoked_at IS NULL",
|
||
|
|
(token_hash(key, token),),
|
||
|
|
).fetchone()
|
||
|
|
return row
|
||
|
|
|
||
|
|
|
||
|
|
def touch(conn: Any, device_id: Any) -> None:
|
||
|
|
conn.execute(
|
||
|
|
"UPDATE devices SET last_seen_at = now() WHERE id = %s"
|
||
|
|
" AND (last_seen_at IS NULL OR last_seen_at < now() - interval '1 minute')",
|
||
|
|
(device_id,),
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def get(conn: Any, device_id: Any) -> dict[str, Any] | None:
|
||
|
|
with conn.cursor(row_factory=dict_row) as cur:
|
||
|
|
row: dict[str, Any] | None = cur.execute(
|
||
|
|
"SELECT id, name, revoked_at FROM devices WHERE id = %s", (device_id,)
|
||
|
|
).fetchone()
|
||
|
|
return row
|
||
|
|
|
||
|
|
|
||
|
|
def revoke(conn: Any, device_id: Any, by: Any) -> bool:
|
||
|
|
row = conn.execute(
|
||
|
|
"UPDATE devices SET revoked_at = now(), revoked_by = %s WHERE id = %s AND revoked_at IS NULL RETURNING id",
|
||
|
|
(by, device_id),
|
||
|
|
).fetchone()
|
||
|
|
return row is not None
|
||
|
|
|
||
|
|
|
||
|
|
def public(row: dict[str, Any] | None) -> dict[str, Any] | None:
|
||
|
|
return None if row is None else {"id": str(row["id"]), "name": row["name"]}
|