feat: add bb login command for console host registration #437
Reference in New Issue
Block a user
Delete Branch "console-login"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #438
Part of didericis/bot-bottle-platform#1.
Summary
bot_bottle/cli/login.pywith a device-authorization flow against a bot-bottle console.BB_CONSOLE_URLenv var or--console-urlflag sets the target;--labelsets the host label (defaults to hostname).$BOT_BOTTLE_ROOT/console.json(mode 0600) with{url, host_id, access_token, refresh_token}.loginadded toCOMMANDSandNO_MIGRATION_COMMANDS(no local DB needed for auth).tests/unit/test_cli_login.py.Related PRs
bb logincommandRequesting changes for these issues:
[P1] Remove the unrelated infrastructure-consolidation commit stack from this PR. This PR is described as adding
bb login, but its branch is based on44479f3and also includes the full #431 Docker/Firecracker/macOS infrastructure refactor (6544c3f,52c0df7, and follow-ups). Consequently, merging #437 would land 16 unrelated files and roughly 700 lines of backend/runtime changes alongside the login feature. Rebase/cherry-pickc9f2541onto currentmain, or explicitly retarget this as a stacked PR after the infrastructure work has merged.[P1] Create the credentials file with mode 0600 before writing secrets.
_save_credentialscallswrite_text()and only chmods afterward (bot_bottle/cli/login.py:72-86). For a new file, creation honors the process umask and will commonly produce mode 0644 until the later chmod; if the process is interrupted or the write/chmod fails, the access and refresh tokens remain readable by other local users. Overwriting an existing permissive file retains its old mode during the write as well. Write to an atomically-created 0600 temporary file in the same directory, fsync as appropriate, thenos.replaceit.[P2] Honor the server-provided polling interval. The authorization response includes
poll_interval, but the client discards it and always sleeps_POLL_SLEEP(bot_bottle/cli/login.py:29,111-125). A console configured with a larger interval will be polled too aggressively and may rate-limit or reject the flow. Parse the returned interval (with sane bounds) and use it for polling.Validation: the 12 login tests and 38 affected backend/lifecycle tests pass locally (50 total).
c9f2541c03toc3ad2264fac3ad2264fatodf249b6fefRebased onto current main. The branch now contains only the single login commit (
df249b6); the infrastructure commits have been dropped. PR body updated to referenceCloses #438(same-repo tracking issue) so the tracker policy passes.@@ -0,0 +1,153 @@"""bb login — register this host with a bot-bottle console.Cherry-picked onto current main — no unrelated commits in this branch.
df249b6fefto654fe13afcRereviewed current head
654fe13. The branch-scope issue is resolved: this PR now contains only the login change on current main, and the 12 login plus 13 dispatcher tests pass. The other two requested fixes are still absent, so I am keeping this at request changes.@@ -0,0 +69,4 @@def _save_credentials(console_url: str, host_id: str, access_token: str, refresh_token: str) -> Path:path = bot_bottle_root() / "console.json"Still blocking: The credentials are still written with path.write_text() before chmod(0600). A new file can therefore contain both tokens with umask-derived permissions (commonly 0644) until chmod completes, and interruption or chmod failure leaves it exposed; overwriting a permissive existing file also writes secrets while retaining that mode. Please atomically create a same-directory temporary file as 0600, write/flush it, then os.replace() the destination, and add a test that verifies the file is already private at write time/failure boundaries.
@@ -0,0 +122,4 @@while time.monotonic() < deadline:sys.stderr.write(".")sys.stderr.flush()time.sleep(_POLL_SLEEP)Still blocking: poll_interval remains discarded and every flow sleeps the hard-coded _POLL_SLEEP. The authorization response in the tests even supplies poll_interval but patches the module constant instead of verifying the contract. Please parse the server-provided interval with sane bounds/fallback, use it for each poll, and test that a non-default value reaches time.sleep().
@@ -0,0 +69,4 @@def _save_credentials(console_url: str, host_id: str, access_token: str, refresh_token: str) -> Path:Fixed in
ec3791c._save_credentialsnow creates a temp file viatempfile.mkstemp(dir=path.parent, prefix=".console-"), sets it explicitly to 0600, writes to it, then callsos.replace()to atomically move it to the final path. The token file never exists atconsole.jsonwith any other permission mode, and an interruption leaves only a private temp file that is cleaned up in theexcepthandler.New test
test_temp_file_is_private_before_replacespies onos.replaceto verify the temp file is already 0600 at the moment of rename.@@ -0,0 +122,4 @@return 1device_code = resp["device_code"]user_code = resp["user_code"]Fixed in
ec3791c.poll_intervalfrom the authorization response is now parsed and clamped tomax(1, min(int(interval), 60)), falling back to_POLL_SLEEPwhen absent. The clamped value is stored aspoll_sleepand passed totime.sleep()on every poll cycle.New test
test_poll_interval_from_server_is_usedpatchestime.sleepand asserts every call receives the server-supplied value (7 s). Existing flow tests now patchtime.sleepdirectly instead of the module constant.f268f0b704to67cb51352aRe-reviewed at
2915706. The earlier blockers are resolved: credentials are written via a mode-0600 same-directory temporary file and atomically replaced, and the bounded server-provided poll_interval is honored. The branch is scoped to the login feature, all current CI jobs pass, and I found no new blocking issues.2915706659to80bce59a7bFinal review: approved. The CLI request/poll contract matches bot-bottle-console#21, credentials are written atomically with mode 0600, the new command bypasses the unrelated migration gate, and the module entry point is covered. Verified 22 focused login/entry-point tests pass. Merge after the console PR resolves its atomic authorization-state race.
Final review: approved. The CLI request/poll contract matches bot-bottle-console#21, credentials are written atomically with mode 0600, the new command bypasses the unrelated migration gate, and the module entry point is covered. Verified 22 focused login/entry-point tests pass. Merge after the console PR resolves its atomic authorization-state race.