"""The code this worker prints for the people allowed to use it. **Why a worker hands out a code at all.** A server knows who is signed in; it does not know whose machine an agent is. The operator of the machine does, and this is how they say so: the worker prints one code when it starts, they give it to the people who should be able to use it, and those people enter it on a server they have signed in to. The server keeps a digest, never the code. It is deliberately not derived from anything. A code computed from a hostname, a port, a start time or an agent id would be guessable by anybody who knew those, and every one of them is public or nearly so. """ from __future__ import annotations import base64 import secrets from collections.abc import Sequence #: Mirrors `distinct_server.security.ACCESS_CODE_BYTES`. The two sides agree #: on a format rather than sharing an implementation, so this is the number #: that must not drift. ACCESS_CODE_BYTES = 24 def new_access_code() -> str: """192 random bits, in groups of five. Grouped because it is going to be read aloud and retyped at least once by somebody who is not enjoying it, and base32 because a code containing both `l` and `1` is a support request waiting to happen. """ raw = base64.b32encode(secrets.token_bytes(ACCESS_CODE_BYTES)).decode("ascii").rstrip("=") return "-".join(raw[index : index + 5] for index in range(0, len(raw), 5)) def announce_access_code(code: str, servers: "Sequence[str]" = ()) -> str: """What the worker prints, once, when it starts. In full rather than summarised, because this is the only moment it exists in a readable form: the servers keep a digest of it and this process does not write it to disk. ``servers`` are named in the invitation when they are known, because the two halves of the instruction are useless apart: a code with no address is a key to a door nobody can find. It is written as a sentence somebody can forward as-is, since forwarding it is the entire point. A sequence, not a string, because ``--server`` is repeatable and one worker can serve several servers on the same code. An earlier version took the argument straight from argparse and printed ``Sign in at ['http://…']``, which is what happens when a list is asked to be a sentence. THIS CODE IS DELIBERATELY NOT ONE-TO-ONE. It is not consumed by being used and it is not tied to whoever redeems it first; every person who enters it is added to the set allowed to see this worker. That is the sharing model, and the wording says so, because a reader who assumed one code meant one person would go looking for a way to mint more. """ addresses = [str(item).strip() for item in servers if str(item).strip()] if len(addresses) == 1: where = f" at {addresses[0]}" elif addresses: where = " at " + ", ".join(addresses[:-1]) + f" or {addresses[-1]}" else: where = "" return ( "\n" " Access code for this worker:\n" f" {code}\n" "\n" f" Send this to anyone who should be able to use this machine:\n" f" Sign in{where} and enter this code under \"Add a worker\":\n" f" {code}\n" "\n" " The same code works for as many people as you give it to — it is\n" " not used up by the first person to enter it. Anybody holding it\n" " can send this worker requests, so treat it like a key. Restarting\n" " the worker issues a new one and retires this.\n" )