···1313## docs
14141515- [architecture](docs/architecture.md)
1616+- [account security](docs/account-security.md)
1617- [development](docs/development.md)
1718- [operations](docs/operations.md)
1819- [invite codes](docs/invite-codes.md)
···102103- Invite codes use the official PDS table shape: code metadata plus recorded
103104 uses. When invites are required, `createAccount` rejects missing, disabled, or
104105 exhausted codes.
106106+- App passwords use durable account-owned rows and can be listed or revoked.
107107+ Revocation also invalidates active sessions created with that app password.
105108- Passkeys are optional account credentials for the OAuth login page. ZDS stores
106109 credential IDs, public keys, counters, names, and last-use timestamps.
107110
+43
docs/account-security.md
···11+# account security
22+33+ZDS keeps account security state in SQLite so credentials and sessions can be
44+listed, revoked, and audited without trusting self-contained tokens alone.
55+66+## sessions
77+88+Password login creates a durable session row keyed by the access and refresh JWT
99+IDs. Refresh rotates that row to new JWT IDs, and revocation marks the stored
1010+row revoked. A session JWT is accepted only when its JTI still has an active
1111+storage row.
1212+1313+The session row also records `auth_method` and optional `app_password_name`.
1414+Account-management endpoints that create, list, or revoke app passwords require
1515+a real password session, matching the official PDS boundary that app passwords
1616+cannot mint or manage other app passwords.
1717+1818+## app passwords
1919+2020+App passwords follow the official PDS model: each row belongs to an account DID
2121+and name, stores a hashed password, creation time, privileged bit, and optional
2222+scope/controller metadata. The raw generated password is returned once from
2323+`com.atproto.server.createAppPassword`.
2424+2525+Supported routes:
2626+2727+- `com.atproto.server.createAppPassword`
2828+- `com.atproto.server.listAppPasswords`
2929+- `com.atproto.server.revokeAppPassword`
3030+3131+`createSession` accepts either the account password or a stored app password.
3232+App-password access JWTs use the official `com.atproto.appPass` or
3333+`com.atproto.appPassPrivileged` scope, while refresh JWTs keep
3434+`com.atproto.refresh`. Sessions created through app passwords are attributed to
3535+the app-password name. Revoking an app password deletes the credential and
3636+revokes active sessions that were created with it.
3737+3838+## references
3939+4040+- official PDS: app passwords are account-owned rows keyed by DID and name, and
4141+ management requires password auth rather than OAuth or app-password auth.
4242+- Tranquil: extends the same account-security plane with scopes, controller DID
4343+ fields, session attribution, and delegation-oriented identity boundaries.
+5-3
docs/architecture.md
···62626363Password sessions and app passwords match the reference PDS account model.
6464Session JWTs are accepted only when their JTI is present in the active session
6565-table, so revocation and refresh rotation can be enforced by storage. The
6666-session schema already carries an optional `controller_did` column for future
6767-delegated-account flows, following Tranquil's controller-vs-subject split.
6565+table, so revocation and refresh rotation can be enforced by storage.
6666+App-password sessions are attributed to the app-password name, and revoking the
6767+credential revokes those sessions too. The session schema already carries an
6868+optional `controller_did` column for future delegated-account flows, following
6969+Tranquil's controller-vs-subject split.
68706971Passkeys are an additional OAuth login credential inspired by Tranquil, backed
7072by the local `webauthn` dependency and stored in SQLite. Discoverable passkey