Gateway: SQLite credential store for the Apple user token #4

Closed
opened 2026-08-09 20:31:47 +00:00 by hermes · 1 comment
Owner

Context

Gateway service (see #18). The gateway stores exactly one Apple user token
(the media-user-token from the login flow) in SQLite.

Task

app/credential_store.py: a CredentialStore (path) class with
load() -> str | None, save(token: str), clear() -> bool:

  • sqlite3 stdlib, table apple_auth (id INTEGER PRIMARY KEY CHECK (id = 1), media_user_token TEXT NOT NULL, updated_at TEXT NOT NULL) — STRICT
  • new files created owner-only (0o600) via the os.open(O_CREAT|O_EXCL)
    pattern; existing files never chmodded, but warn if group/other bits are
    set (mirror beets-playlistmanager/database.py::open_database)
  • transactional writes (BEGIN IMMEDIATE/commit/rollback, like
    playlistmanager's PlaylistStore)

Acceptance criteria

  • Unit tests: save/load round-trip, clear, file created with mode 0o600,
    warning on a world-readable existing file, load() returns None on a
    missing file.
  • Schema applies cleanly to a fresh DB; no ORM, no migration framework.
## Context Gateway service (see #18). The gateway stores exactly one Apple user token (the `media-user-token` from the login flow) in SQLite. ## Task `app/credential_store.py`: a `CredentialStore` (path) class with `load() -> str | None`, `save(token: str)`, `clear() -> bool`: - sqlite3 stdlib, table `apple_auth (id INTEGER PRIMARY KEY CHECK (id = 1), media_user_token TEXT NOT NULL, updated_at TEXT NOT NULL)` — STRICT - new files created owner-only (0o600) via the `os.open(O_CREAT|O_EXCL)` pattern; existing files never chmodded, but warn if group/other bits are set (mirror `beets-playlistmanager/database.py::open_database`) - transactional writes (BEGIN IMMEDIATE/commit/rollback, like playlistmanager's `PlaylistStore`) ## Acceptance criteria - Unit tests: save/load round-trip, clear, file created with mode 0o600, warning on a world-readable existing file, `load()` returns None on a missing file. - Schema applies cleanly to a fresh DB; no ORM, no migration framework.
Author
Owner

Done in coop/apple-music-gateway (commit c8db51c): app/credential_store.py — CredentialStore(path) with load() -> str | None, save(token) -> updated_at, clear() -> bool. Single-row STRICT table apple_auth (id CHECK (id = 1), media_user_token, updated_at); transactional writes (BEGIN IMMEDIATE/commit/rollback); new files created owner-only via os.open(O_CREAT|O_EXCL); existing files never chmodded but warn (InsecureCredentialStoreWarning) when group/other bits are set; lazy store — load() on a missing file returns None and creates nothing. 11 unit tests cover round-trip, clear, 0600 creation, warning on world/group-readable files (mode left untouched), missing-file load, idempotent schema on an existing file, single-row upsert, empty-token rejection, and :memory:.

Done in `coop/apple-music-gateway` (commit `c8db51c`): `app/credential_store.py` — `CredentialStore(path)` with `load() -> str | None`, `save(token) -> updated_at`, `clear() -> bool`. Single-row STRICT table `apple_auth (id CHECK (id = 1), media_user_token, updated_at)`; transactional writes (BEGIN IMMEDIATE/commit/rollback); new files created owner-only via `os.open(O_CREAT|O_EXCL)`; existing files never chmodded but warn (`InsecureCredentialStoreWarning`) when group/other bits are set; lazy store — `load()` on a missing file returns None and creates nothing. 11 unit tests cover round-trip, clear, 0600 creation, warning on world/group-readable files (mode left untouched), missing-file load, idempotent schema on an existing file, single-row upsert, empty-token rejection, and `:memory:`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coop/beets-appleplaylists#4
No description provided.