-
Notifications
You must be signed in to change notification settings - Fork 0
Profiles
A profile remembers the two harmless halves of a canteen login — the username and the canteen number — so neither the command line nor a script has to carry them. The password is not one of those halves: it goes to the operating system's credential store, or — on a machine that has none — into the profile file encrypted under a passphrase. It is never written in the clear unless you explicitly ask for that.
Profiles work from the command line and from Python.
strava-cz profile add skola --username your.username --canteen 3753 --verifyYou are asked for the password on the terminal (it is not echoed), and --verify logs
in once to check it before anything is stored.
strava-cz profile list
strava-cz profile show skola # never prints the password
strava-cz profile default doma # which one --profile defaults to
strava-cz profile remove doma # also clears its keyring entryThe first profile you add becomes the default, and a single profile is the default whether or not it is marked as one.
--secret chooses the store. Nothing here writes the password in the clear unless you
name file yourself.
--secret |
where the password goes | remembered? |
|---|---|---|
auto (default)
|
the keyring if there is one, otherwise encrypted
|
yes |
keyring |
the OS credential store | yes |
encrypted |
encrypted into the profile file, under a passphrase | yes |
env |
nowhere; read from $STRAVA_CZ_PASSWORD each run |
no |
prompt |
nowhere; asked for each run | no |
file |
plaintext in the profile file | yes |
auto is a strategy, not a stored value. It prints which way it went and writes the
resolved source into the profile, so profile show always names the real location.
keyring uses the keyring package, which talks to
GNOME Keyring or KWallet on Linux, the Keychain on macOS and the Credential Locker on
Windows. Install it with the cli extra:
pip install "strava-cz[cli]"file writes the password in the clear. The file is chmod 0600, which keeps other
users out, but it does not stop anyone who can read your home directory or a backup of
it — and the command says so, loudly, every time you use it. There is rarely a reason to
choose it over encrypted.
--secret encrypted is the answer for a machine with no credential store. The password
is encrypted with AES-256-GCM under a key that scrypt derives from a passphrase
you choose:
"encrypted": {
"cipher": "aes-256-gcm",
"kdf": {"name": "scrypt", "n": 32768, "r": 8, "p": 1, "dklen": 32, "salt": "…"},
"nonce": "…",
"ciphertext": "…"
}- A fresh salt and nonce are drawn for every save, so storing the same password twice produces two records that look nothing alike.
- The KDF parameters live in the record, so raising the work factor in a later release still leaves older profiles readable.
- GCM authenticates as well as encrypts. A damaged or edited record is rejected with a clear message instead of decrypting to nonsense.
- Both primitives come from vetted implementations — scrypt from the standard library's
hashlib, AES-GCM from cryptography, which thecliextra installs. Nothing here invents a construction of its own.
The passphrase is looked up exactly as the password is:
$STRAVA_CZ_PASSPHRASE_<PROFILE>, then $STRAVA_CZ_PASSPHRASE, then a prompt. When you
create the profile it is asked for twice, so a typo cannot lock you out of it.
What this does and does not buy you. It protects the profile file at rest — a backup, a Syncthing replica, a copied home directory, a
profiles.jsonthat ends up somewhere it should not. Anyone holding only that file has nothing usable.It does not protect you from someone who can already read your environment or this process's memory while it runs. In cron the passphrase sits in a variable much as the password would; the difference is that the stored file is then safe to back up and sync, which the plaintext one never is.
To change the passphrase, add the profile again under the same name — it is replaced, and re-encrypted under the new one.
$XDG_CONFIG_HOME/strava-cz/profiles.json # ~/.config/... when XDG_CONFIG_HOME is unset
The file is created 0600 inside a 0700 directory and replaced atomically, so an
interrupted write cannot leave half a store behind. A keyring-backed profile looks like
this — note what is missing:
{
"default": "skola",
"profiles": {
"skola": {
"canteen_number": "3753",
"language": "EN",
"secret": "keyring",
"username": "your.username"
}
}
}--profile-store PATH points at a different file, which is useful for testing and for
keeping a service account separate from your own.
In this order, first hit wins:
-
$STRAVA_CZ_PASSWORD_<PROFILE>— the profile name upper-cased, with anything that is not a letter or digit turned into_. Soskolareads$STRAVA_CZ_PASSWORD_SKOLA. -
$STRAVA_CZ_PASSWORD. - Whatever the profile's
secretsays — the keyring, the encrypted record, the plaintext file, or a prompt.
The environment always wins, including over a keyring profile. That is deliberate: a cron job or a container can drive an existing profile without rewriting it, and one job can drive several accounts by setting the per-profile variable for each.
A server with no desktop session usually has no working keyring — often there is no
credential-store daemon installed at all, so there is nothing to unlock. The default
--secret auto detects this before storing anything, says so, and encrypts instead:
$ strava-cz profile add skola --username your.username --canteen 3753
no OS keyring on this machine, so the password will be encrypted into the profile file
with a passphrase you choose (--secret keyring to insist on a keyring, --secret env to
store nothing at all)
Password for your.username at canteen 3753:
Passphrase for profile skola:
Passphrase again:
saved profile skola (your.username @ canteen 3753), password in encrypted in the profile file
unlock it without being asked by setting $STRAVA_CZ_PASSPHRASE_SKOLA (or
$STRAVA_CZ_PASSPHRASE) - useful in cron
Nothing is ever silently written somewhere less safe, and --secret keyring still means
keyring: asking for it explicitly on such a machine fails rather than quietly becoming
something else.
A profile created on a laptop with a keyring also keeps working when the file is copied
to such a server, as long as $STRAVA_CZ_PASSWORD is set there — rule 1 above never
needs the keyring.
from strava_cz import StravaCZ
with StravaCZ.from_profile("skola") as strava: # omit the name for the default
strava.menu.fetch()
strava.menu.print()from_profile() accepts the same keyword arguments as StravaCZ, plus:
| argument | meaning |
|---|---|
store |
a ProfileStore to read from, instead of the default location |
password |
skip the lookup entirely and use this |
allow_prompt |
set False where nothing can answer a prompt, so it fails instead of hanging |
Managing profiles directly:
from strava_cz import Profile, ProfileStore, SecretSource
store = ProfileStore() # or ProfileStore("/path/to/profiles.json")
store.save(Profile("skola", "your.username", "3753"), password="...")
store.list() # [Profile(name='skola', ...)]
store.get() # the default profile
store.get("skola")
store.set_default("skola")
store.resolve_password(store.get("skola")) # follows the order above
store.resolve_passphrase(store.get("skola")) # for an encrypted profile
store.delete("skola") # forgets the keyring entry tooProfile is a frozen dataclass whose repr() never contains the password, so it cannot
leak into a traceback or a log line.
| exception | when |
|---|---|
ProfileNotFoundError |
no such profile, or no default is set |
KeyringUnavailableError |
no usable keyring; the message lists the alternatives |
EncryptionUnavailableError |
cryptography is not installed, so nothing can be encrypted |
ProfileError |
anything else — a bad name, a corrupt store, no password to be found |
All of them derive from StravaError, so except StravaError still catches everything.
On the command line they all exit with code 6.