texas2step 2.2.4__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {texas2step-2.2.4 → texas2step-2.3.0}/PKG-INFO +1 -1
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/access.md +114 -7
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/api.md +5 -4
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/configuration.md +4 -2
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/install.md +35 -8
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/methodology.md +8 -1
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/roadmap.md +13 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/security.md +10 -4
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/traffic.md +3 -1
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/__init__.py +1 -1
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/access.py +47 -20
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/access.py +59 -15
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/meta.py +6 -2
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/wire.py +11 -5
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/app.py +8 -5
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/cli.py +188 -13
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/cluster.py +18 -5
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/config.py +14 -4
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/config_template.toml +4 -1
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/lockouts.py +86 -8
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/rtc.py +19 -5
- texas2step-2.3.0/src/texas2step/static/assets/app.css +1 -0
- texas2step-2.3.0/src/texas2step/static/assets/app.js +8 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/index.html +2 -2
- texas2step-2.2.4/src/texas2step/static/assets/app.css +0 -1
- texas2step-2.2.4/src/texas2step/static/assets/app.js +0 -8
- {texas2step-2.2.4 → texas2step-2.3.0}/.gitignore +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/LICENSE.md +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/README.md +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/nginx/texas2step-http.conf +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/nginx/texas2step-log.conf +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/nginx/texas2step-speedtest.conf +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/nginx/texas2step.conf +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/texas2step-update.service +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/texas2step-update.timer +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/texas2step.default +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/deploy/texas2step.service +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/development.md +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/experience.md +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/images/hero-dark.jpg +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/images/results-dark.jpg +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/images/results-light.jpg +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/docs/images/upload-light.jpg +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/legacy/README.md +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/pyproject.toml +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/__main__.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/__init__.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/measure.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/api/results.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/config_edit.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/config_merge.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/engine.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/iface.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/logs.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/page.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/payload.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/quality.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/LICENSE +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/NOTICE +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js.LICENSE.txt +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui.css +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/static/favicon.svg +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/stats.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/store.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/traffic.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/update.py +0 -0
- {texas2step-2.2.4 → texas2step-2.3.0}/src/texas2step/words.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: texas2step
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Web-centric throughput, latency, jitter and packet-loss measurement between endpoints.
|
|
5
5
|
Project-URL: Homepage, https://github.com/robert-patrick-texas/texas2step
|
|
6
6
|
Project-URL: Documentation, https://robert-patrick-texas.github.io/texas2step/
|
|
@@ -18,7 +18,8 @@ no user names or passwords. The desk's own tools stay on your networks: saved re
|
|
|
18
18
|
works out the same code from it, with nothing shared between them while they run.
|
|
19
19
|
* **A code buys a pass.** The visitor enters the code once. The server answers with a
|
|
20
20
|
pass, good for 12 hours (`access_pass_hours`), which the browser then sends with every
|
|
21
|
-
request, to every server in the picker. A server with the same key accepts it.
|
|
21
|
+
request, to every server in the picker. A server with the same key accepts it. To end
|
|
22
|
+
every pass at once, see [Ending every pass](#ending-every-pass).
|
|
22
23
|
* **The desk's tools stay inside.** Visitors from addresses in `internal_networks` need no
|
|
23
24
|
code, and only they get History, the API tab, saved results and the code itself.
|
|
24
25
|
Everyone else gets the Test, Monitor and Diag tabs.
|
|
@@ -88,7 +89,8 @@ To turn codes off again: `sudo texas2step config set access_code=off --write`, t
|
|
|
88
89
|
| `access_timezone` | `America/New_York` | The time zone whose midnight starts a new code; the same on every server |
|
|
89
90
|
| `access_words` | `""` | Your own word list; empty uses the built-in words |
|
|
90
91
|
| `access_digits` | `2` | The digits that end a code: 2, 3 or 4 |
|
|
91
|
-
| `access_pass_hours` | `12` | How long
|
|
92
|
+
| `access_pass_hours` | `12` | How long a pass lasts from when it was issued (1 to 168). Each server applies its own as passes are used, so a change applies to passes already given; use the same on every server. |
|
|
93
|
+
| `access_note` | `Contact the service desk for assistance.` | The line under the code box, such as how to reach the desk; `""` for none |
|
|
92
94
|
| `access_key_file` | `/etc/texas2step/access.key` | The key |
|
|
93
95
|
| `access_phrases_file` | `/etc/texas2step/access-phrases.toml` | [Standing phrases](#standing-phrases), if the file exists |
|
|
94
96
|
| `internal_networks` | `[]` | Your own networks: no code, and the desk's tools. A list of IPv4 and IPv6 addresses and networks in CIDR form, mixed freely: `["192.168.0.0/16", "2001:db8::/32", "203.0.113.7"]`. An address alone is a single host. Bits past the prefix are ignored, so `10.2.1.30/24` means all of `10.2.1.0/24`. Give the addresses as the server sees them: staff who come in through your firewall's NAT arrive from its public address. |
|
|
@@ -112,8 +114,9 @@ tests until it changes, but for a short list, or a `fixed` code, consider
|
|
|
112
114
|
`access_digits = 3` or `4`.
|
|
113
115
|
|
|
114
116
|
**When it changes.** A new code starts at midnight in `access_timezone`. The previous
|
|
115
|
-
code keeps working for 24 hours after the change, for a call that spans midnight.
|
|
116
|
-
`fixed` code
|
|
117
|
+
code keeps working for 24 hours after the change, for a call that spans midnight. To
|
|
118
|
+
change it before then, or to change a `fixed` code, [shuffle the codes](#shuffling-the-codes),
|
|
119
|
+
or make a new key:
|
|
117
120
|
|
|
118
121
|
```bash
|
|
119
122
|
sudo texas2step access key --new
|
|
@@ -123,6 +126,42 @@ That makes a new key at once: every code and pass from the old one stops working
|
|
|
123
126
|
the new key to the other servers as in step 1, and restart the service on each. Without
|
|
124
127
|
`--yes`, it asks first.
|
|
125
128
|
|
|
129
|
+
### Shuffling the codes
|
|
130
|
+
|
|
131
|
+
When a code has gone further than it should, shuffle the codes. The current code and the
|
|
132
|
+
previous one stop working at once, and a new one takes their place:
|
|
133
|
+
|
|
134
|
+
```console
|
|
135
|
+
$ sudo texas2step access shuffle
|
|
136
|
+
Shuffle the codes? The current and previous codes stop working, and the desk reads the new one. [y/N] y
|
|
137
|
+
Codes shuffled here. Today's code is now copper.lantern.42; the codes before it don't work.
|
|
138
|
+
Passes already given keep working: add --end-passes to end them as well.
|
|
139
|
+
Other servers: sent to dallas, houston.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
A shuffle is a random value mixed into every code from then on. It is kept in
|
|
143
|
+
`access_lockouts_file`, so it needs no new key and no restart, and outlasts a restart.
|
|
144
|
+
Every code entered after it is checked against it, on every worker. The desk reads the
|
|
145
|
+
new code at the top of History, or with `sudo texas2step access show`, which also says
|
|
146
|
+
when the codes were shuffled. There is no previous code until the next change: at
|
|
147
|
+
midnight, the shuffled code becomes yesterday's and works for 24 hours as usual. Phrases
|
|
148
|
+
are not codes, so they keep working.
|
|
149
|
+
|
|
150
|
+
Every server must hold the same shuffle, or they give different codes. With
|
|
151
|
+
[servers that defend together](#servers-that-defend-together), it goes to every server in
|
|
152
|
+
`[[locations]]`, and each keeps the latest it is given. Without them, the command prints
|
|
153
|
+
the value for the others; run that on each:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
sudo texas2step access shuffle --to 3f9a0c7e12b4d856
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`access show`'s fingerprint includes the shuffle, so servers that agree show the same one.
|
|
160
|
+
|
|
161
|
+
Passes already given keep working. Add `--end-passes` to [end them](#ending-every-pass)
|
|
162
|
+
too, which has everyone outside enter the new code: the same as a new key, without
|
|
163
|
+
copying a key to every server.
|
|
164
|
+
|
|
126
165
|
**Typing it.** Case and separators don't matter: `Maple River 42`, `maple-river-42` and
|
|
127
166
|
`maple.river.42` are the same code, and `7` counts for `07`.
|
|
128
167
|
|
|
@@ -204,7 +243,8 @@ journalctl -u texas2step --grep 'access phrase'
|
|
|
204
243
|
stays guessable: several words that don't belong together, such as
|
|
205
244
|
`copper lantern harbor violet`, rather than `vip access`. texas2step takes any phrase
|
|
206
245
|
you choose. To withdraw one, `sudo texas2step access phrase remove vip` on every server,
|
|
207
|
-
and restart
|
|
246
|
+
and restart. Passes it already bought last until they run out (`access_pass_hours`),
|
|
247
|
+
unless you [end every pass](#ending-every-pass).
|
|
208
248
|
A new key doesn't change the phrases.
|
|
209
249
|
|
|
210
250
|
A phrase buys the same pass as the day's code, so the visitor gets what any visitor
|
|
@@ -273,6 +313,30 @@ inside.
|
|
|
273
313
|
Without `internal_networks`, every visitor needs a code, and anyone with one can read the
|
|
274
314
|
saved results. `texas2step access show` warns about that.
|
|
275
315
|
|
|
316
|
+
### Inside on one address family
|
|
317
|
+
|
|
318
|
+
A visitor with both IPv4 and IPv6 comes from two addresses, and `internal_networks` may
|
|
319
|
+
list only one of them: an office's IPv6 network, say, but not the NAT address its IPv4
|
|
320
|
+
leaves by. The page asks its own server, at the address it was opened at, whether the
|
|
321
|
+
code is needed, and the browser reaches that over either family. When the answer is no
|
|
322
|
+
but another address the page tests refuses it, the page asks for the code over itself,
|
|
323
|
+
in a box the visitor can put aside with **Not now**. That other address is this server's
|
|
324
|
+
IPv4-only or IPv6-only name ([IPv4 and IPv6](configuration.md#ipv4-and-ipv6)), or another
|
|
325
|
+
server in the picker. The pass the code buys works over both families and on every
|
|
326
|
+
server with the same key.
|
|
327
|
+
|
|
328
|
+
The box comes up by itself until the visitor puts it aside, which holds until the page
|
|
329
|
+
is loaded again. Meanwhile Test and Monitor say the function is restricted over that
|
|
330
|
+
address, with a button to enter the code, and Diag's rows for it say **Restricted**, with
|
|
331
|
+
the same button.
|
|
332
|
+
Diag then tries that address every 10 seconds instead of every second, and opens no
|
|
333
|
+
WebSocket there until the code is in. A server with `access_code = "off"` has no pass to
|
|
334
|
+
give, so its page doesn't ask: it only says which address wants a code.
|
|
335
|
+
|
|
336
|
+
A pass that hasn't run out but is refused anyway, as from a network blocked by hand
|
|
337
|
+
([lockouts and blocks](#lockouts-and-blocks)), isn't asked for again by itself. To have
|
|
338
|
+
neither family asked, list both of a site's addresses in `internal_networks`.
|
|
339
|
+
|
|
276
340
|
## Lockouts and blocks
|
|
277
341
|
|
|
278
342
|
Wrong codes count against the visitor's network, its IPv4 /24 or IPv6 /48, not its
|
|
@@ -377,6 +441,44 @@ every 30 minutes. One with 10,000 networks gets about 1.4 million a day:
|
|
|
377
441
|
For a server open to the Internet, use `access_digits = 4`, and standing phrases of
|
|
378
442
|
several unrelated words.
|
|
379
443
|
|
|
444
|
+
## Ending every pass
|
|
445
|
+
|
|
446
|
+
A pass holds the time it was issued, signed with the key: `1793030400.q7GxN0x2rY8vLmKp3TfWcA9b`.
|
|
447
|
+
No server keeps a list of them. Each server checks a pass as it is used:
|
|
448
|
+
|
|
449
|
+
* The signature matches, so a server with the same key issued it.
|
|
450
|
+
* It was issued less than `access_pass_hours` ago, by this server's setting now. Lower
|
|
451
|
+
the setting, and restart, and passes already given end sooner; raise it, and they last
|
|
452
|
+
longer.
|
|
453
|
+
* It wasn't issued more than 5 minutes ahead of this server's clock.
|
|
454
|
+
* It was issued after passes were last ended.
|
|
455
|
+
|
|
456
|
+
To end every pass given so far, such as when a code or a phrase went further than it
|
|
457
|
+
should have:
|
|
458
|
+
|
|
459
|
+
```console
|
|
460
|
+
$ sudo texas2step access passes --end
|
|
461
|
+
End every pass issued so far? Everyone testing from outside enters the code again. [y/N] y
|
|
462
|
+
Every pass issued before 2026-10-25 14:02 EDT is ended here, within seconds; tests running with one stop too.
|
|
463
|
+
Other servers: sent to dallas, houston.
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
The time is kept in `access_lockouts_file`, beside the lockouts, so it needs no restart
|
|
467
|
+
and outlasts one. Each of the service's workers reads it within 5 seconds. From then on,
|
|
468
|
+
a pass issued before it is refused as if there were none (`401`), and the WebSockets and
|
|
469
|
+
UDP sessions opened with one close within a few seconds. The page asks for the code
|
|
470
|
+
again. The codes and phrases still work, so everyone with the current code is back in at
|
|
471
|
+
once, with a new pass. Visitors from `internal_networks` need no pass, so nothing changes
|
|
472
|
+
for them.
|
|
473
|
+
|
|
474
|
+
With [servers that defend together](#servers-that-defend-together), the end goes to every
|
|
475
|
+
server in `[[locations]]`, and each keeps the later of its own and the one it is sent.
|
|
476
|
+
Without them, run the command on each server: until then, the others still take the
|
|
477
|
+
passes. `sudo texas2step access passes` says how long passes last here and when they were
|
|
478
|
+
last ended, and `access show` does too. To end the codes too, use
|
|
479
|
+
`sudo texas2step access shuffle --end-passes` ([Shuffling the codes](#shuffling-the-codes)).
|
|
480
|
+
A new key also ends every pass, and every code with it.
|
|
481
|
+
|
|
380
482
|
## Servers that defend together
|
|
381
483
|
|
|
382
484
|
When a lockout begins on one server, the others can block that network too, so an
|
|
@@ -391,8 +493,9 @@ access_peer_networks = ["198.51.100.20", "2001:db8:20::20", "203.0.113.30"]
|
|
|
391
493
|
* **What goes to the others.** A lockout here goes, as a block, to every server in
|
|
392
494
|
`[[locations]]`, at once. While the attacker keeps trying here, it goes again every
|
|
393
495
|
third of `access_lockout_minutes`, so the others' blocks don't run out first.
|
|
394
|
-
`sudo texas2step access block
|
|
395
|
-
`--clear` on any one of them lets a caller
|
|
496
|
+
`sudo texas2step access block`, `access lockouts --clear`, `access passes --end` and
|
|
497
|
+
`access shuffle` go to every server too: `--clear` on any one of them lets a caller
|
|
498
|
+
back in everywhere. The
|
|
396
499
|
[API's blocks](#lockouts-and-blocks) stay on the server they are sent to. A block from
|
|
397
500
|
`access block` ends passes on every server, as here; a lockout ends none anywhere. A
|
|
398
501
|
server on an earlier version sends either as a lockout.
|
|
@@ -407,6 +510,10 @@ access_peer_networks = ["198.51.100.20", "2001:db8:20::20", "203.0.113.30"]
|
|
|
407
510
|
compromised: a block from another server covers at most a /24 or a /48, never
|
|
408
511
|
`internal_networks` or loopback, lasts at most a week, and isn't passed on. A server
|
|
409
512
|
holds at most 1,000 of them. A wider block by hand stays on the server where it was set.
|
|
513
|
+
Another server can end every pass issued before its request, which at worst has
|
|
514
|
+
everyone enter the code again, but not set that time later than the receiving server's
|
|
515
|
+
clock allows, which would stamp new passes there too far ahead. It can shuffle the
|
|
516
|
+
codes, which at worst has the desk read the new one.
|
|
410
517
|
* **Counts stay on each server.** Wrong codes count where they are entered, so with three
|
|
411
518
|
servers an attacker gets up to about seven guesses, two at each and one more, before
|
|
412
519
|
the whole cluster locks them out.
|
|
@@ -78,7 +78,8 @@ sending the offer. The offer must include data channels:
|
|
|
78
78
|
profiles the session runs. A voice or video session counts toward a
|
|
79
79
|
[traffic limit](traffic.md#monitors-call-profiles). The answer is
|
|
80
80
|
`{"op":"profile","ok":true,"name":"voice"}`, or `"ok": false` with a `reason`: the
|
|
81
|
-
server withholds
|
|
81
|
+
server withholds that profile (`monitor_voice` or `monitor_video` is `false`, as
|
|
82
|
+
`/api/v1/info`'s `monitor` says), or a traffic limit is reached, when
|
|
82
83
|
the site's `message` and `url` come with it. Past a limit while a session runs, the
|
|
83
84
|
server sends `{"op":"limit","message":…,"url":…}` unasked and from then on echoes only
|
|
84
85
|
probes of up to 64 bytes.
|
|
@@ -91,7 +92,7 @@ every port in its `rtc_port_min`–`rtc_port_max` range is taken.
|
|
|
91
92
|
|
|
92
93
|
| Method | Path | Returns |
|
|
93
94
|
|---|---|---|
|
|
94
|
-
| GET | `/api/v1/info` | name, version (left out for clients outside `internal_networks`), server identity, capabilities (`stamps`: the UDP probe can [stamp](#udp-probe-protocol) its echoes), limits, ICE servers, UI defaults (`one_stream`: whether a test downloads over one stream too, unless asked otherwise), `monitor` (`profile`: Monitor's until a visitor picks; `video`: whether the Video call
|
|
95
|
+
| GET | `/api/v1/info` | name, version (left out for clients outside `internal_networks`), server identity, capabilities (`stamps`: the UDP probe can [stamp](#udp-probe-protocol) its echoes), limits, ICE servers, UI defaults (`one_stream`: whether a test downloads over one stream too, unless asked otherwise), `monitor` (`profile`: Monitor's until a visitor picks; `voice`, `video`: whether the Voice call and Video call profiles are offered); with a [traffic limit](traffic.md), `traffic` (below) |
|
|
95
96
|
| GET | `/api/v1/whoami` | client IP, port, family, scheme, HTTP version, user agent |
|
|
96
97
|
| GET | `/api/v1/diag/http` | both ends of this HTTP connection, its HTTP version and TLS, the server's TCP RTT and clock (below) |
|
|
97
98
|
| GET | `/api/v1/locations` | servers offered in the picker (this one first) |
|
|
@@ -187,13 +188,13 @@ clients that would need a pass.
|
|
|
187
188
|
|
|
188
189
|
| Method | Path | Purpose |
|
|
189
190
|
|---|---|---|
|
|
190
|
-
| GET | `/api/v1/access?pass=<pass>` | `{"required", "desk"}`: whether this client needs a pass,
|
|
191
|
+
| GET | `/api/v1/access?pass=<pass>` | `{"required", "desk", "codes", "note"}`: whether this client needs a pass, gets the desk's functions, and whether this server gives passes at all (`access_code` is on), and the site's line under the code box (`access_note`, empty for none); with `pass`, also whether that pass is valid and when it expires |
|
|
191
192
|
| POST | `/api/v1/access` | `{"code": "maple.river.42"}` (any case and separators, or a standing phrase) returns `{"pass", "expires"}`. A wrong code is `403`. After `access_lockout_after` wrong codes in a row from one /24 or /48, every code from it gets that same `403`, unread, until `access_lockout_minutes` pass without one ([Lockouts and blocks](access.md#lockouts-and-blocks)). A body over 1 KB is `413` |
|
|
192
193
|
| GET | `/api/v1/access/code` | The current code, when it changes, and the previous one while it still works; only from `internal_networks` |
|
|
193
194
|
| GET | `/api/v1/access/blocks` | `{"lockouts": [{"network", "source", "wrong_codes", "since", "until", "by_hand"}], "counting"}` (`by_hand`: a block, which ends the passes used from the network too); only from `internal_networks` and the server itself |
|
|
194
195
|
| POST | `/api/v1/access/blocks` | `{"network": "198.51.100.0/24", "minutes": 120}` locks a network out by hand, or keeps its lockout going, and ends the passes used from it; `minutes` defaults to `access_lockout_minutes`. One that overlaps `internal_networks`, or is wider than a /16 or a /32, is `400` |
|
|
195
196
|
| DELETE | `/api/v1/access/blocks?network=<network>` | Ends the lockouts that overlap it (`all`: every one); returns `{"cleared": [...]}` |
|
|
196
|
-
| POST | `/api/v1/access/peer` | For [servers that defend together](access.md#servers-that-defend-together): `{"from", "to", "action": "block" \| "clear" \| "check", "network", "minutes", "kind": "lockout" \| "block"}` (`kind`: a block set by hand, which ends passes there too; `lockout` when left out), signed in `X-Texas2step-Time`, `-Nonce` and `-Signature`. Only from `access_peer_networks`, with `access_share_lockouts` on; `404` to everyone else |
|
|
197
|
+
| POST | `/api/v1/access/peer` | For [servers that defend together](access.md#servers-that-defend-together): `{"from", "to", "action": "block" \| "clear" \| "check" \| "end-passes" \| "shuffle", "network", "minutes", "kind": "lockout" \| "block", "at", "value"}` (`kind`: a block set by hand, which ends passes there too; `lockout` when left out. `end-passes` ends every pass issued before `at`, in Unix seconds; `shuffle` mixes `value`, 16 hex digits, into the codes from `at`, the latest shuffle staying; neither `at` may be later than the receiving server's clock allows), signed in `X-Texas2step-Time`, `-Nonce` and `-Signature`. Only from `access_peer_networks`, with `access_share_lockouts` on; `404` to everyone else |
|
|
197
198
|
|
|
198
199
|
```bash
|
|
199
200
|
pass=$(curl -s -X POST https://speedtest.example.net/texas2step/api/v1/access \
|
|
@@ -38,7 +38,8 @@ file up to date with the new version's template and keeps your values; see
|
|
|
38
38
|
| `page_test` / `page_monitor` / `page_diag` / `page_history` / `page_api` | `true` | Show that tab of the web UI. Any can be hidden, but not all. A hidden page's address opens the first page shown. The API stays on: `results_enabled` and `measure_enabled` turn parts of it off, and `/api/docs` stays reachable without the API tab. |
|
|
39
39
|
| `default_streams` / `default_seconds` / `default_packets` | 4 / 10 / 500 | Defaults advertised by `/api/v1/info` |
|
|
40
40
|
| `monitor_profile` | `path` | Monitor's profile until a visitor picks one: `path` (small probes, to follow the path), `voice` or `video` (a call's packets, scored every 10 seconds); see [Live monitor](methodology.md#live-monitor) |
|
|
41
|
-
| `
|
|
41
|
+
| `monitor_voice` | `true` | Offer Monitor's Voice call profile, about 150 kb/s each way while a tab runs it. With `false`, the page shows no Voice call button and the server refuses the profile. |
|
|
42
|
+
| `monitor_video` | `true` | Offer Monitor's Video call profile, about 1.4 Mb/s each way while a tab runs it. With `false`, the page shows no Video call button and the server refuses the profile. With both `false`, Monitor runs Path only, and shows no profile switch. Voice and video count toward a [traffic limit](traffic.md#monitors-call-profiles). |
|
|
42
43
|
| `default_one_stream` | `true` | Whether a test also downloads over [one stream](methodology.md#one-stream), unless the visitor chooses in Test options or the command line with `--one-stream` or `--no-one-stream`. It can add up to half again to a test's traffic, so a server whose traffic is billed may want `false`; see [Traffic limits](traffic.md#what-a-test-moves). |
|
|
43
44
|
| `cors_origins` | `["*"]` | Origins allowed to call the API from a browser |
|
|
44
45
|
| `trusted_proxies` | `["127.0.0.1", "::1"]` | Peers whose `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Prefix` and `X-Client-Protocol` are believed |
|
|
@@ -48,7 +49,8 @@ file up to date with the new version's template and keeps your values; see
|
|
|
48
49
|
| `access_timezone` | `America/New_York` | The time zone whose midnight starts a new code |
|
|
49
50
|
| `access_words` | `""` | A file of your own words for codes, one entry per line; empty uses the built-in words |
|
|
50
51
|
| `access_digits` | 2 | The digits that end a code: 2, 3 or 4 |
|
|
51
|
-
| `access_pass_hours` | 12 | How long
|
|
52
|
+
| `access_pass_hours` | 12 | How long a pass lasts from when it was issued; applied as passes are used, so a change applies to passes already given. See [Ending every pass](access.md#ending-every-pass). |
|
|
53
|
+
| `access_note` | `Contact the service desk for assistance.` | The line under the code box, such as how to reach the desk; `""` for none |
|
|
52
54
|
| `access_key_file` | `/etc/texas2step/access.key` | The key codes come from; the same on every server of an organization |
|
|
53
55
|
| `access_phrases_file` | `/etc/texas2step/access-phrases.toml` | Standing phrases that work as codes, if the file exists; see [Standing phrases](access.md#standing-phrases) |
|
|
54
56
|
| `access_lockout_after` | 3 | Wrong codes in a row from one /24 or /48 that lock it out of entering codes; see [Lockouts and blocks](access.md#lockouts-and-blocks) |
|
|
@@ -51,7 +51,8 @@ To do the same by hand, follow steps 1 to 5 below instead.
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
It logs to `texas2step-install.log` beside itself. When it finishes, the service
|
|
54
|
-
answers both on `127.0.0.1:8080` and through nginx, and it
|
|
54
|
+
answers both on `127.0.0.1:8080` (or the `--port` you gave) and through nginx, and it
|
|
55
|
+
prints the address to open.
|
|
55
56
|
You can run it again safely: it skips each step that is already done.
|
|
56
57
|
|
|
57
58
|
What the check looks at, and what the script then does:
|
|
@@ -66,7 +67,7 @@ What the check looks at, and what the script then does:
|
|
|
66
67
|
| Peers | Each `--peer URL` is asked who it is (`/api/v1/info`). A peer that can't be reached is left out, with the command to add it later. | `texas2step config add-location ... --write` |
|
|
67
68
|
| nginx | nginx is installed and running, its configuration passes `nginx -t`, and it loads `conf.d/*.conf`. It finds your HTTPS site, and checks that nothing else on that site uses `/texas2step/`. If texas2step is already published, nginx is left alone. | Adds `include snippets/texas2step.conf;` after the site's `server_name` line, runs `nginx -t` and reloads. The file is backed up to `/var/backups/texas2step/` first, and put back if the test fails. |
|
|
68
69
|
| UDP | The probe's port range: the one you set, or 61000–61999. It checks this range against the firewall and the kernel's ephemeral ports. | Pins the range in the settings. Opens it in ufw or firewalld if one of them is active. |
|
|
69
|
-
| Port
|
|
70
|
+
| Port | Nothing else listens on texas2step's TCP port: 8080, `LISTEN_PORT` in `/etc/default/texas2step`, or `--port`. nginx's `upstream` in `/etc/nginx/conf.d/texas2step.conf` points at that port. | With `--port`, sets `LISTEN_PORT` and the `upstream` before the package is installed, so the service never starts on 8080. An `upstream` that points elsewhere is pointed at the port. |
|
|
70
71
|
|
|
71
72
|
Options:
|
|
72
73
|
|
|
@@ -79,6 +80,7 @@ Options:
|
|
|
79
80
|
| `--path PATH` | Publish texas2step at `PATH` instead of `/texas2step/`, e.g. `/speedtest`. The check moves the package's snippet to that path and shows it in the install script, which writes it as `snippets/texas2step-at-speedtest.conf` and includes that. Each later run writes it again from the new package. Use the same path in `--url-v4` and `--url-v6`. |
|
|
80
81
|
| `--no-nginx` | Install the service only, and publish it yourself |
|
|
81
82
|
| `--udp-ports MIN-MAX` | The probe's UDP range, e.g. `61000-61999` |
|
|
83
|
+
| `--port PORT` | The TCP port texas2step listens on, on `127.0.0.1` behind nginx, when another program needs 8080. Choose one from 1024 to 32767. Later runs and `texas2step update` keep it. See [Another port](#another-port). |
|
|
82
84
|
| `--url-v4 URL`, `--url-v6 URL` | This server's IPv4-only and IPv6-only base URLs, for the header's IP drop-down; see [IPv4 and IPv6](configuration.md#ipv4-and-ipv6). The check looks up each name: it should have only an A record (or only AAAA), pointing at this server. After the install, the script checks that nginx answers for each name. |
|
|
83
85
|
| `--no-firewall` | Leave the firewall alone; the report lists the rule to add |
|
|
84
86
|
| `--allow-downgrade` | Allow installing an older version than the one installed |
|
|
@@ -128,6 +130,32 @@ yourself.
|
|
|
128
130
|
To make the second server by cloning the first's virtual machine instead, see
|
|
129
131
|
[Clone a server](#clone-a-server).
|
|
130
132
|
|
|
133
|
+
### Another port
|
|
134
|
+
|
|
135
|
+
texas2step listens on `127.0.0.1:8080`, and nginx reaches it there through the
|
|
136
|
+
`upstream` in `/etc/nginx/conf.d/texas2step.conf`. When another program needs 8080,
|
|
137
|
+
give the setup tool another port, the same way for a new install or an installed server:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
sudo ./texas2step-setup --port 8081
|
|
141
|
+
sudo ./texas2step-install.sh
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The install script sets `LISTEN_PORT=8081` in `/etc/default/texas2step` and points the
|
|
145
|
+
`upstream` at `127.0.0.1:8081`, keeping a dated copy of each file it changes in
|
|
146
|
+
`/var/backups/texas2step/`. It does this before installing the package, so the service
|
|
147
|
+
never starts on 8080, even on a new install. Then it restarts texas2step, reloads nginx
|
|
148
|
+
and checks both.
|
|
149
|
+
|
|
150
|
+
Choose a port from 1024 to 32767. texas2step runs as its own user, which can't listen
|
|
151
|
+
below 1024, and Linux takes the ports of outgoing connections from 32768 up. If the port
|
|
152
|
+
is in use, the check says by what. Both files are kept on upgrades, and the setup tool
|
|
153
|
+
and `texas2step update` read the port from `LISTEN_PORT`. When a new version changes the
|
|
154
|
+
`upstream` file, the update installs the new one with your port in it.
|
|
155
|
+
|
|
156
|
+
If you changed `LISTEN_PORT` by hand and not the `upstream`, the next run of the setup
|
|
157
|
+
tool points the `upstream` at it.
|
|
158
|
+
|
|
131
159
|
## 1. Install the package
|
|
132
160
|
|
|
133
161
|
Download the newest `.deb` from the
|
|
@@ -264,12 +292,11 @@ choose the default theme (Light, Dark, America, Energy, Federal, Military or Tex
|
|
|
264
292
|
that site directly. Sites can also measure each other with `POST /api/v1/measure/full`,
|
|
265
293
|
but only toward listed locations.
|
|
266
294
|
|
|
267
|
-
**Listen address and workers.** These are set in `/etc/default/texas2step`.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
it.
|
|
295
|
+
**Listen address and workers.** These are set in `/etc/default/texas2step`. To move the
|
|
296
|
+
service to another port, see [Another port](#another-port). More workers help on
|
|
297
|
+
multi-gigabit links, where Python's per-connection CPU cost is the limit. Each worker
|
|
298
|
+
keeps its own UDP sessions, which is fine, because a session lives entirely in the
|
|
299
|
+
worker that answered it.
|
|
273
300
|
|
|
274
301
|
## 4. Firewall and UDP
|
|
275
302
|
|
|
@@ -272,7 +272,10 @@ counted as lost. Statistics cover the visible window (1, 5 or 15 minutes).
|
|
|
272
272
|
A **profile** sets what is sent. Anyone can switch it beside the Start button. Options
|
|
273
273
|
holds the exact sizes and rates, and the page's address carries them, so the service
|
|
274
274
|
desk can send a caller a link such as `/monitor?profile=voice`. The site picks the
|
|
275
|
-
profile a visitor starts with (`monitor_profile`)
|
|
275
|
+
profile a visitor starts with (`monitor_profile`), and can withhold the call profiles
|
|
276
|
+
(`monitor_voice`, `monitor_video`): the switch then shows only the ones offered, and none
|
|
277
|
+
at all when only Path is. A link or a saved choice for a withheld profile starts with
|
|
278
|
+
the nearest one offered.
|
|
276
279
|
|
|
277
280
|
| Profile | Sends | Each way, as the network carries it |
|
|
278
281
|
|---|---|---|
|
|
@@ -365,6 +368,10 @@ and closes its connections. Its rows and events stay, and the timelines show the
|
|
|
365
368
|
An attempt not answered within 2 s (1.5 s for WebSocket and UDP) counts as missed. A
|
|
366
369
|
method that has answered before shows a miss as loss (red). One that hasn't answered
|
|
367
370
|
yet stays grey and keeps trying. One the server doesn't offer is grey and isn't tried.
|
|
371
|
+
One whose address asks for the access code, as a server's IPv4-only name can for a
|
|
372
|
+
visitor inside on IPv6 only, says **Restricted** (amber) and doesn't count as loss:
|
|
373
|
+
its HTTPS is tried every 10 s until the code is in, and no WebSocket is opened there
|
|
374
|
+
meanwhile ([Inside on one address family](access.md#inside-on-one-address-family)).
|
|
368
375
|
The clock offset is the server's time minus the browser's at the middle of the round
|
|
369
376
|
trip, ± half the round trip.
|
|
370
377
|
|
|
@@ -116,6 +116,19 @@ links and footer credit. Three places still say texas2step:
|
|
|
116
116
|
|
|
117
117
|
Newest first. Each release's notes have the details and its upgrade steps.
|
|
118
118
|
|
|
119
|
+
### [2.3.0](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.3.0) (2026-10-25)
|
|
120
|
+
|
|
121
|
+
- [x] Passes carry when they were issued: `access_pass_hours` applies to passes already
|
|
122
|
+
given, and `texas2step access passes --end` ends every pass, on every server with
|
|
123
|
+
lockouts shared.
|
|
124
|
+
- [x] `texas2step access shuffle` changes the codes at once, without a new key.
|
|
125
|
+
- [x] A visitor inside on one address family and outside on the other is asked for the
|
|
126
|
+
code in a box over the page; Diag says which address is restricted.
|
|
127
|
+
- [x] The code screen's words are the organization's, with `access_note` under the box.
|
|
128
|
+
- [x] `texas2step-setup --port` for servers where another program needs 8080.
|
|
129
|
+
- [x] `monitor_voice` withholds Monitor's Voice call profile, as `monitor_video` does
|
|
130
|
+
the Video call one.
|
|
131
|
+
|
|
119
132
|
### [2.2.4](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.2.4) (2026-10-25)
|
|
120
133
|
|
|
121
134
|
- [x] The desk's functions refuse requests made for pages on other sites; with
|
|
@@ -37,14 +37,20 @@ pass.
|
|
|
37
37
|
the service (`0640 root:texas2step`). Anyone holding it can work out every code, so it
|
|
38
38
|
is kept out of the world-readable `config.toml`. `texas2step access show` prints a
|
|
39
39
|
fingerprint to compare servers by, which reveals nothing about the key.
|
|
40
|
-
* **Passes.** A right code buys a pass for `access_pass_hours` (12 by default):
|
|
41
|
-
|
|
40
|
+
* **Passes.** A right code buys a pass for `access_pass_hours` (12 by default): the time
|
|
41
|
+
it was issued, signed with the key, checked by every server that holds it against its
|
|
42
|
+
`access_pass_hours` as it is then. The page keeps it in
|
|
42
43
|
local storage, with no cookie, and sends it to its own server in an `Authorization`
|
|
43
44
|
header (a WebSocket's in its subprotocol), so it stays out of URLs that proxies log;
|
|
44
45
|
to the other servers, as `?pass=`. A pass is a bearer token: it isn't tied to
|
|
45
|
-
an address or a browser, and works until it
|
|
46
|
+
an address or a browser, and works until it runs out. A
|
|
46
47
|
[block set by hand](access.md#lockouts-and-blocks) ends it for the network blocked,
|
|
47
|
-
WebSockets and UDP sessions already open included.
|
|
48
|
+
WebSockets and UDP sessions already open included.
|
|
49
|
+
[`texas2step access passes --end`](access.md#ending-every-pass) ends every pass issued
|
|
50
|
+
so far, on every server with lockouts shared, while the codes keep working.
|
|
51
|
+
[`texas2step access shuffle`](access.md#shuffling-the-codes) replaces the current and
|
|
52
|
+
previous codes with a new one, and with `--end-passes` ends the passes too. A new access
|
|
53
|
+
key ends every pass and code.
|
|
48
54
|
* **Standing phrases.** For the desk away from a PC, in their own `0640` file
|
|
49
55
|
(`access-phrases.toml`), with an optional last day. Each use is a journal warning with
|
|
50
56
|
the phrase's name, never the phrase. The examples published in the docs are refused.
|
|
@@ -142,7 +142,9 @@ acknowledgements the server sends back, as measured over IPv6. A tab left runnin
|
|
|
142
142
|
When the limit is reached, the server holds those sessions to the small probes of the
|
|
143
143
|
Path profile, which aren't counted, and Monitor says so with the site's message and link.
|
|
144
144
|
Until the limit opens again, a call profile is refused at its start.
|
|
145
|
-
`monitor_video = false` in the settings withholds the Video call profile altogether
|
|
145
|
+
`monitor_video = false` in the settings withholds the Video call profile altogether, and
|
|
146
|
+
`monitor_voice = false` the Voice call one: the page doesn't offer them, and the server
|
|
147
|
+
refuses them. With both, Monitor runs only the uncounted Path profile.
|
|
146
148
|
|
|
147
149
|
## What isn't counted
|
|
148
150
|
|
|
@@ -51,6 +51,12 @@ WORDS = Path(__file__).with_name("words.txt")
|
|
|
51
51
|
KEY_BYTES = 32
|
|
52
52
|
DRAW = 512 # built-in words each organization draws by its key
|
|
53
53
|
GRACE = 24 * 3600 # the previous code still works this long after a change
|
|
54
|
+
# How far ahead of this server's clock a pass may be stamped: another server's clock, or
|
|
55
|
+
# one that issued it just after passes were ended there (Codes.issue).
|
|
56
|
+
PASS_AHEAD = 300
|
|
57
|
+
# The codes' shuffle (texas2step access shuffle): a random value, the same on every
|
|
58
|
+
# server, mixed into each code. Not secret: the codes still need the key.
|
|
59
|
+
SHUFFLE = re.compile(r"[0-9a-f]{16}")
|
|
54
60
|
MIN_SITE_WORDS = 100
|
|
55
61
|
MAX_ENTRY = 40
|
|
56
62
|
GROUP = "texas2step" # the service's group, which must read the key
|
|
@@ -376,8 +382,9 @@ class Codes:
|
|
|
376
382
|
return Period(f"{first:%Y-%m}", self._midnight(first), self._midnight(following))
|
|
377
383
|
return Period("fixed", None, None)
|
|
378
384
|
|
|
379
|
-
def code_for(self, period: Period) -> str:
|
|
380
|
-
|
|
385
|
+
def code_for(self, period: Period, shuffle: str = "") -> str:
|
|
386
|
+
"""The period's code; with ``shuffle``, a different one, nobody's before it."""
|
|
387
|
+
mac = self._mac("code", self.interval, period.id, *([shuffle] if shuffle else []))
|
|
381
388
|
n = len(self.words)
|
|
382
389
|
first = int.from_bytes(mac[0:8]) % n
|
|
383
390
|
parts = [self.words[first]]
|
|
@@ -387,21 +394,24 @@ class Codes:
|
|
|
387
394
|
number = int.from_bytes(mac[16:24]) % 10**self.digits
|
|
388
395
|
return ".".join([*parts, f"{number:0{self.digits}d}"])
|
|
389
396
|
|
|
390
|
-
def current(self, now: datetime) -> Current:
|
|
397
|
+
def current(self, now: datetime, shuffle: str = "", shuffled_at: float = 0) -> Current:
|
|
398
|
+
"""The period's code, and the previous period's in its grace day. Shuffled in this
|
|
399
|
+
period, there is no previous one: the codes given before the shuffle end with it."""
|
|
391
400
|
period = self.period(now)
|
|
392
401
|
previous = until = None
|
|
393
|
-
|
|
394
|
-
|
|
402
|
+
grace = period.start is not None and now < period.start + timedelta(seconds=GRACE)
|
|
403
|
+
if grace and period.start is not None and shuffled_at < period.start.timestamp():
|
|
404
|
+
previous = self.code_for(self.period(period.start - timedelta(seconds=1)), shuffle)
|
|
395
405
|
until = period.start + timedelta(seconds=GRACE)
|
|
396
|
-
return Current(self.code_for(period), period, previous, until)
|
|
406
|
+
return Current(self.code_for(period, shuffle), period, previous, until)
|
|
397
407
|
|
|
398
|
-
def check(self, text: str, now: datetime) -> bool:
|
|
408
|
+
def check(self, text: str, now: datetime, shuffle: str = "", shuffled_at: float = 0) -> bool:
|
|
399
409
|
"""Whether ``text`` is the current code, or the previous one in its grace day."""
|
|
400
410
|
given = normalize(text)
|
|
401
411
|
head, _, tail = given.rpartition(".")
|
|
402
412
|
if tail.isdigit() and len(tail) <= self.digits: # "7" for "07"
|
|
403
413
|
given = f"{head}.{int(tail):0{self.digits}d}"
|
|
404
|
-
current = self.current(now)
|
|
414
|
+
current = self.current(now, shuffle, shuffled_at)
|
|
405
415
|
return any(
|
|
406
416
|
hmac.compare_digest(given.encode(), code.encode())
|
|
407
417
|
for code in (current.code, current.previous)
|
|
@@ -420,7 +430,7 @@ class Codes:
|
|
|
420
430
|
def expired(self, phrase: Phrase, now: datetime) -> bool:
|
|
421
431
|
return phrase.until is not None and now.astimezone(self.zone).date() > phrase.until
|
|
422
432
|
|
|
423
|
-
def fingerprint(self) -> str:
|
|
433
|
+
def fingerprint(self, shuffle: str = "") -> str:
|
|
424
434
|
"""The same on every server that would give the same codes; reveals nothing."""
|
|
425
435
|
words = hashlib.sha256("\n".join(self.words).encode()).hexdigest()
|
|
426
436
|
what = (
|
|
@@ -432,27 +442,44 @@ class Codes:
|
|
|
432
442
|
if self.phrases: # without any, as before there were phrases
|
|
433
443
|
standing = sorted(f"{p.code}|{p.until or ''}" for p in self.phrases)
|
|
434
444
|
what += (hashlib.sha256("\n".join(standing).encode()).hexdigest(),)
|
|
445
|
+
if shuffle: # without one, as before codes could be shuffled
|
|
446
|
+
what += (shuffle,)
|
|
435
447
|
mac = self._mac("fingerprint", *what, words).hex()
|
|
436
448
|
return f"{mac[0:4]}-{mac[4:8]}-{mac[8:12]}"
|
|
437
449
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
450
|
+
# A pass is "<issued>.<signature>": when it was issued, in Unix seconds, signed with
|
|
451
|
+
# the key. How long it lasts is decided when it is checked, by access_pass_hours as
|
|
452
|
+
# it is then, so a shorter setting shortens passes already given and a longer one
|
|
453
|
+
# lengthens them. Passes issued before passes were last ended are refused.
|
|
454
|
+
|
|
455
|
+
def issue(self, now: float | None = None, ended: float = 0) -> tuple[str, int]:
|
|
456
|
+
"""A new pass, and when it runs out here. Stamped after ``ended``, when passes
|
|
457
|
+
were last ended, even by a clock a little behind the server that ended them."""
|
|
458
|
+
issued = max(int(now if now is not None else time.time()), int(ended) + 1)
|
|
459
|
+
return f"{issued}.{self._sign(issued)}", issued + self.pass_seconds
|
|
460
|
+
|
|
461
|
+
def _sign(self, issued: int) -> str:
|
|
462
|
+
# Not the "pass" of earlier versions, which signed the expiry: such a pass must
|
|
463
|
+
# not be read as one issued at that time.
|
|
464
|
+
mac = self._mac("pass-issued", str(issued))[:18]
|
|
444
465
|
return base64.urlsafe_b64encode(mac).decode()
|
|
445
466
|
|
|
446
|
-
def
|
|
447
|
-
"""
|
|
467
|
+
def pass_issued(self, token: str, now: float | None = None, ended: float = 0) -> int | None:
|
|
468
|
+
"""When the pass was issued, if it is ours and still good: issued after ``ended``,
|
|
469
|
+
within access_pass_hours, and not more than PASS_AHEAD seconds ahead of now."""
|
|
448
470
|
stamp, _, sig = (token or "").partition(".")
|
|
449
471
|
if not stamp.isdigit() or len(stamp) > 12:
|
|
450
472
|
return None
|
|
451
|
-
|
|
473
|
+
issued = int(stamp)
|
|
452
474
|
now = now if now is not None else time.time()
|
|
453
|
-
if
|
|
475
|
+
if issued <= ended or issued > now + PASS_AHEAD or now >= issued + self.pass_seconds:
|
|
454
476
|
return None
|
|
455
|
-
return
|
|
477
|
+
return issued if hmac.compare_digest(sig.encode(), self._sign(issued).encode()) else None
|
|
478
|
+
|
|
479
|
+
def pass_expires(self, token: str, now: float | None = None, ended: float = 0) -> int | None:
|
|
480
|
+
"""When the pass runs out here, if it is still good (pass_issued)."""
|
|
481
|
+
issued = self.pass_issued(token, now, ended)
|
|
482
|
+
return None if issued is None else issued + self.pass_seconds
|
|
456
483
|
|
|
457
484
|
|
|
458
485
|
# --- Who is inside -----------------------------------------------------------------
|