docs: add tokenserver documentation to sync (#1681)
Some checks are pending
Glean probe-scraper / glean-probe-scraper (push) Waiting to run

docs: add tokenserver documentation to sync
This commit is contained in:
Taddes 2025-04-17 19:33:08 -04:00 committed by GitHub
parent cb37e2aa41
commit dadbcea3f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 386 additions and 18 deletions

181
Cargo.lock generated
View File

@ -268,6 +268,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstream"
version = "0.6.18"
@ -363,6 +372,17 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.4.0"
@ -392,22 +412,25 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bindgen"
version = "0.69.5"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags 2.9.0",
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"itertools",
"clap",
"env_logger 0.9.3",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.99",
"which",
]
[[package]]
@ -554,6 +577,21 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "cmake"
version = "0.1.54"
@ -907,6 +945,19 @@ dependencies = [
"regex",
]
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "env_logger"
version = "0.11.6"
@ -935,6 +986,16 @@ dependencies = [
"serde 1.0.218",
]
[[package]]
name = "errno"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "findshlibs"
version = "0.10.2"
@ -1155,6 +1216,8 @@ dependencies = [
[[package]]
name = "grpcio-sys"
version = "0.13.0+1.56.2-patched"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3dae9132320ae1b03ea55b5ddc88ca72a31fb85fa631a241a40157f5feffe43"
dependencies = [
"bindgen",
"boringssl-src",
@ -1231,6 +1294,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
@ -1267,6 +1339,15 @@ dependencies = [
"digest",
]
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "hostname"
version = "0.4.0"
@ -1623,15 +1704,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -1745,6 +1817,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "litemap"
version = "0.7.5"
@ -2021,6 +2099,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "1.0.1"
@ -2449,6 +2533,19 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.23"
@ -2862,6 +2959,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
@ -3021,7 +3124,7 @@ version = "0.18.2"
dependencies = [
"async-trait",
"cadence",
"env_logger",
"env_logger 0.11.6",
"futures 0.3.31",
"hostname",
"lazy_static",
@ -3067,7 +3170,7 @@ dependencies = [
"diesel",
"diesel_logger",
"diesel_migrations",
"env_logger",
"env_logger 0.11.6",
"futures 0.3.31",
"http 1.2.0",
"slog-scope",
@ -3099,7 +3202,7 @@ dependencies = [
"backtrace",
"cadence",
"deadpool",
"env_logger",
"env_logger 0.11.6",
"form_urlencoded",
"futures 0.3.31",
"google-cloud-rust-raw",
@ -3152,6 +3255,24 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -3310,7 +3431,7 @@ dependencies = [
"diesel",
"diesel_logger",
"diesel_migrations",
"env_logger",
"env_logger 0.11.6",
"futures 0.3.31",
"http 1.2.0",
"serde 1.0.218",
@ -3495,6 +3616,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unindent"
version = "0.2.4"
@ -3605,6 +3732,12 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.5"
@ -3745,6 +3878,18 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -0,0 +1,68 @@
# Tokenserver
## What is Tokenserver?
Tokenserver is responsible for allocating Firefox Sync users to Sync Storage nodes hosted in our Spanner GCP Backend.
Tokenserver provides the "glue" between [Firefox Accounts](https://github.com/mozilla/fxa/) and the
[SyncStorage API](https://mozilla-services.readthedocs.io/en/latest/storage/apis-1.5.html).
Broadly, Tokenserver is responsible for:
* Checking the user's credentials as provided by FxA.
* Sharding users across storage nodes in a way that evenly distributes server load.
* Re-assigning the user to a new storage node if their FxA encryption key changes.
* Cleaning up old data from deleted accounts.
The service was originally conceived to be a general-purpose mechanism for connecting users
to multiple different Mozilla-run services, and you can see some of the historical context
for that original design [here](https://wiki.mozilla.org/Services/Sagrada/TokenServer)
and [here](https://mozilla-services.readthedocs.io/en/latest/token/index.html).
In practice today, it is only used for connecting to Sync.
## Tokenserver Crates & Their Purpose
### `tokenserver-auth`
Handles authentication logic, including:
- Token generation and validation.
- Ensuring clients are authorized before accessing Sync services.
### `tokenserver-common`
Provides shared functionality and types used across the Tokenserver ecosystem:
- Common utility functions.
- Structs and traits reused in other Tokenserver modules.
### `tokenserver-db`
Responsible for persisting and retrieving authentication/session-related data securely and efficiently.
Manages all database interactions for Tokenserver:
- Database schema definitions.
- Connection pooling and querying logic.
### `tokenserver-settings`
Handles configuration management:
- Loads and validates settings for Tokenserver.
- Supports integration with different deployment environments.
## How Tokenserver Handles Failure Cases
### Token Expiry
When a Tokenserver token expires, Sync Storage returns a 401 code, requiring clients to get a new token. Then, clients would use their FxA OAuth Access tokens to generate a new token, if the FxA Access Token is itself expired, then Tokenserver returns a 401 itself.
### User revoking access token
The user could revoke the access token by signing out using the Mozilla Accounts Manage Account settings. In that case, clients continue to sync up to the expiry time, which is one hour. To mitigate against this case, Firefox clients currently receive push notifications from FxA instructing them to disconnect. Additionally, any requests done against FxA itself (for example to get the users profile data, connected devices, etc) will also trigger the client to disconnect.
### User Changes Their Password
This is similar to the case where users revoke their access tokens. Any devices with a not-expired access token will continue to sync until expiry, but clients will likely disconnect those clients faster than the 1 hour - however, a malicious user might be able to sync upwards of 1 hour.
### User Forgetting Their Password (without a recovery key)
When a user forgets and resets their password without a recovery key, their Sync keys change. The Tokenserver request includes the key ID (which is a hash of the sync key). Thus, on the next sync, Tokenserver recognizes that the password changed, and ensures that the tokens it issues point users to a new location on Sync Storage. In practice, it does that by including the Key ID itself in the Tokenserver token, which is then sent to Sync Storage.
### User Forgetting Their Password (with a recovery key)
When a user forgets and resets their password, but has their recovery key, the behavior is similar to the password change and user revoking token cases.
## Utilities
Tokenserver has two regular running utility scripts:
1 - [Process Account Events](../tools/process_account_events.md)
2 - [Purge Old Records](../tools/purge_old_records_tokenserver.md)
For context on these processes, their purpose, and how to run them, please review their documentation pages.

View File

@ -0,0 +1,155 @@
# Token Server API v1.0
Unless stated otherwise, all APIs are using application/json for the requests
and responses content types.
**GET** **/1.0/<app_name>/<app_version>**
Asks for new token given some credentials in the Authorization header.
By default, the authentication scheme is Mozilla Accounts OAuth 2.0
but other schemes can
potentially be used if supported by the login server.
- **app_name** is the name of the application to access, like **sync**.
- **app_version** is the specific version number of the api that you want
to access.
The first /1.0/ in the URL defines the version of the authentication
token itself.
Example for Mozilla Account OAuth 2.0::
```
GET /1.0/sync/1.5
Host: token.services.mozilla.com
Authorization: bearer <assertion>
```
This API returns several values in a json mapping:
- **id** -- a signed authorization token, containing the
user's id for the application and the node.
- **key** -- a secret derived from the shared secret
- **uid** -- the user id for this service
- **api_endpoint** -- the root URL for the user for the service.
- **duration** -- the validity duration of the issued token, in seconds.
Example::
```
HTTP/1.1 200 OK
Content-Type: application/json
{'id': <token>,
'key': <derived-secret>,
'uid': 12345,
'api_endpoint': 'https://db42.sync.services.mozilla.com/1.5/12345',
'duration': 300,
}
```
If the **X-Client-State** header is included in the request, the
server will compare the submitted value to any previously-seen value.
If it has changed then a new uid and api_endpoint are generated, in
effect "resetting" the node allocation for this user.
## Request Headers
**X-Client-State**
An optional string that can be sent to identify a unique configuration
of client-side state. It may be up to 32 characters long, and must
contain only characters from the urlsafe-base64 alphabet (i.e.
alphanumeric characters, underscore and hyphen) and the period.
A change in the value of this header may cause the user's node
allocation to be reset, keeping in mind Sync currently has a single node.
Clients should include any client-side state
that is necessary for accessing the selected app. For example, clients
accessing :ref:`server_syncstorage_api_15` would include a hex-encoded
hash of the encryption key in this header, since a change in the encryption
key will make any existing data unreadable.
Updated values of the **X-Client-State** will be rejected with an error
status of **"invalid-client-state"** if:
* The proposed new value is in the server's list of previously-seen
client-state values for that user.
* The client-state is missing or empty, but the server has previously
seen a non-empty client-state for that user.
* The user's IdP provides generation numbers in their identity
certificates, and the changed client-state value does not correspond
to an increase in generation number.
## Response Headers
**Retry-After**
When sent together with an HTTP 503 status code, this header signifies that
the server is undergoing maintenance. The client should not attempt any
further requests to the server for the number of seconds specified in
the header value.
**X-Backoff**
This header may be sent to indicate that the server is under heavy load
but is still capable of servicing requests. Unlike the **Retry-After**
header, **X-Backoff** may be included with any type of response, including
a **200 OK**.
Clients should avoid unnecessary requests to the server for the number of seconds
specified in the header value. For example, clients may avoid pre-emptively
refreshing token if an X-Backoff header was recently seen.
**X-Timestamp**
This header will be included with all "200" and "401" responses, giving
the current POSIX timestamp as seen by the server, in seconds. It may
be useful for client to adjust their local clock when generating authorization
assertions.
## Error Responses
===============
All errors are also returned, wherever possible, as json responses following the
structure `described in Cornice
<https://cornice.readthedocs.io/en/latest/validation.html#dealing-with-errors>`_.
In cases where generating such a response is not possible (e.g. when a request
if so malformed as to be unparsable) then the resulting error response will
have a *Content-Type* that is not **application/json**.
The top-level JSON object in the response will always contain a key named
`status`, which maps to a string identifying the cause of the error. Unexpected
errors will have a `status` string of "error"; errors expected as part of
the protocol flow will have a specific `status` string as detailed below.
Error status codes and their corresponding output are:
- **404** : unknown URL, or unsupported application.
- **400** : malformed request. Possible causes include a missing
option, bad values or malformed json.
- **401** : authentication failed or protocol not supported.
The response in that case will contain WWW-Authenticate headers
(one per supported scheme) and may report the following `status`
strings:
- **"invalid-credentials"**: authentication failed due to invalid
credentials e.g. a bad signature on the Authorization assertion.
- **"invalid-timestamp"**: authentication failed because the included
timestamp differed too greatly from the server's current time.
- **"invalid-generation"**: authentication failed because the server
has seen credentials with a more recent generation number.
- **"invalid-client-state"**: authentication failed because the server
has seen an updated value of the *X-Client-State* header.
- **"new-users-disabled"**: authentication failed because the user has
not been seen previously on this server, and new user accounts have
been disabled in the application config.
- **405** : unsupported method
- **406** : unacceptable - the client asked for an Accept we don't support
- **503** : service unavailable (ldap or snode backends may be down)