Files
PacketStation/server/packetstation/auth/devices.py
T

64 lines
2.1 KiB
Python
Raw Normal View History

"""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"]}