Tempest Navigator 4.0 - Reference Documentation

Reference file: tokens.md

Tokens

This page covers operational token handling for Tempest deployment and account migration work. Keep raw tokens out of Git, logs, tickets, screenshots, and chat. Use .sandbox/ or a local password manager for temporary working files.

Token Types

Common tokens in deployment and migration:

  • ADMIN_TOKEN: raw operator token for Tempest admin routes. Railway stores only TEMPEST_ADMIN_TOKEN_HASH.
  • account accessJwt: short-lived bearer token returned by com.atproto.server.createSession.
  • account refreshJwt: refresh token returned by session creation and refresh.
  • serviceAuth: scoped proof from one PDS to another, returned by com.atproto.server.getServiceAuth.
  • PLC operation token/code: short-lived one-time authorization for com.atproto.identity.signPlcOperation, requested from the current PDS during did:plc identity migration.
  • app password: Bluesky-compatible account password substitute for client and bot login. Prefer this over the main account password for migration commands when the source PDS accepts it.

Source Account Access Token

To migrate an account from its current PDS, first create a normal session on the current authoritative PDS. For the current tempestpds.bsky.social migration example:

export OLD_PDS="https://jellybaby.us-east.host.bsky.network"
export OLD_AUTH_PDS="$OLD_PDS"
export OLD_LOGIN_PDS="$OLD_AUTH_PDS"
export HANDLE="tempestpds.bsky.social"
export OLD_IDENTIFIER="$HANDLE"
export TEMPEST="https://tempest.desertthunder.dev"
export TEMPEST_SERVICE_DID="did:web:tempest.desertthunder.dev"

read -rs OLD_PASSWORD
export OLD_PASSWORD

Use the main account password for PLC operation signing. App passwords can be useful for ordinary source-PDS access, but they may produce a session that cannot request a PLC operation signature.

Do not assign passwords with unquoted shell syntax. Characters like $, !, backticks, and backslashes can be expanded or interpreted by the shell. Prefer silent input:

unset OLD_PASSWORD
read -rs OLD_PASSWORD
export OLD_PASSWORD

If assigning directly, use single quotes:

export OLD_PASSWORD='literal-password-with-$N'

If the source PDS asks for an auth-factor token/code during login, pass it to the CLI as OLD_AUTH_FACTOR_TOKEN:

read -rs OLD_AUTH_FACTOR_TOKEN
export OLD_AUTH_FACTOR_TOKEN

The migration CLI reads the same environment variables as the curl examples and writes the same artifacts:

uv run --project scripts tempest login-source

Use the account password or a Bluesky app password:

curl -fsS -X POST "$OLD_PDS/xrpc/com.atproto.server.createSession" \
  -H "Content-Type: application/json" \
  --data "$(jq -n \
    --arg identifier "$HANDLE" \
    --arg password "$OLD_PASSWORD" \
    '{identifier: $identifier, password: $password}')" \
  > .sandbox/old_session.json

Extract the source access token:

export OLD_ACCESS="$(jq -r .accessJwt .sandbox/old_session.json)"

Check that extraction worked without printing the token:

jq '{did, handle, has_access: (.accessJwt != null), has_refresh: (.refreshJwt != null)}' \
  .sandbox/old_session.json

Service Auth for Migration

Ask the old PDS for service auth scoped to account creation on Tempest:

uv run --project scripts tempest service-auth

The equivalent curl call is:

curl -fsS -G "$OLD_PDS/xrpc/com.atproto.server.getServiceAuth" \
  -H "Authorization: Bearer $OLD_ACCESS" \
  --data-urlencode "aud=$TEMPEST_SERVICE_DID" \
  --data-urlencode "lxm=com.atproto.server.createAccount" \
  > .sandbox/service_auth_create_account.json

aud must be a DID. Do not use https://tempest.desertthunder.dev as the getServiceAuth audience; current PDS implementations reject URL audiences with InvalidRequest.

Check that a token exists without printing it:

jq '{has_token: (.token != null)}' .sandbox/service_auth_create_account.json

Export the token for the next Tempest request:

export SERVICE_AUTH="$(jq -r .token .sandbox/service_auth_create_account.json)"

Use that value as serviceAuth when calling Tempest com.atproto.server.createAccount with an existing DID. The service-auth token must have:

  • issuer and subject equal to the account DID;
  • audience equal to the target service DID, such as did:web:tempest.desertthunder.dev;
  • method (lxm) equal to com.atproto.server.createAccount.

Admin Token Hash

Generate TEMPEST_ADMIN_TOKEN_HASH through the same uv project:

uv run --project scripts tempest argon

The ar and arg2 aliases run the same helper:

uv run --project scripts tempest ar --only-hash

PLC Operation Token

After repo import and blob upload, the did:plc document must be updated so #atproto_pds points at Tempest. The source PDS signs that PLC operation after issuing a short-lived token or emailing a one-time code:

uv run --project scripts tempest plc-recommended
uv run --project scripts tempest plc-request-token

If .sandbox/plc_token.json contains a token, the CLI will read it. If the source PDS emails a code instead, keep it out of shell history when possible and export it only for the signing step:

read -rs PLC_TOKEN
export PLC_TOKEN
uv run --project scripts tempest plc-sign

The signed operation is written to .sandbox/plc_signed_operation.json. Inspect its service endpoint before submitting it:

jq '.operation.services.atproto_pds.endpoint' .sandbox/plc_signed_operation.json

For this deployment the value must be https://tempest.desertthunder.dev.

If plc-request-token returns Bad token scope, the source session is not authorized for PLC signing. Re-run login-source with the main account password and any required OLD_AUTH_FACTOR_TOKEN, then retry plc-request-token. If handle login returns Invalid identifier or password, set OLD_IDENTIFIER to the source account email address and retry login-source.

After a successful login-source, verify whether the same token works for ordinary old-PDS auth:

uv run --project scripts tempest source-session-status

If that succeeds while plc-request-token fails, the saved source session is valid and the source PDS is specifically refusing PLC signing for that token scope.

Local lexicon references:

  • priv/lexicons/official/com/atproto/identity/requestPlcOperationSignature.json defines this as an authenticated no-input procedure that requests an emailed code.
  • priv/lexicons/official/com/atproto/identity/signPlcOperation.json defines the token field consumed by signing.
  • priv/lexicons/official/com/atproto/server/createSession.json defines the optional authFactorToken used during session creation.

If the repository host rejects main-password login, try a separate source login host while leaving OLD_PDS unchanged for repo and blob export and OLD_AUTH_PDS unchanged for authenticated old-PDS operations:

export OLD_PDS="https://jellybaby.us-east.host.bsky.network"
export OLD_AUTH_PDS="$OLD_PDS"
export OLD_LOGIN_PDS="https://bsky.social"
uv run --project scripts tempest login-source
uv run --project scripts tempest plc-request-token

Safety Notes

  • Do not commit .sandbox/old_session.json, .sandbox/service_auth_create_account.json, .sandbox/plc_token.json, .sandbox/plc_signed_operation.json, or shell history containing raw tokens.

  • Prefer app passwords over the main account password for source-PDS session creation.

  • Revoke or rotate the app password after migration.

  • Treat serviceAuth as short-lived migration material. Regenerate it if the migration attempt is delayed.

  • Treat PLC operation tokens/codes as single-use, short-lived migration material. Regenerate the token/code if signing fails or the token expires.

  • Treat Tempest accessJwt as short-lived. If migration commands return Bearer token is invalid or Bearer token is expired, refresh the saved Tempest session:

    uv run --project scripts tempest refresh-session
    
  • Keep the old PDS account active until Tempest passes repo, blob, firehose, crawler, DID, and real-client checks.

Related Runbooks

Start tempest docs / tempest.desertthunder.dev 2026-06-24 18:47:36Z