Repository navigation
feat(service-accounts): Add an endpoint to revoke service account tokens - #130508
Merged
Merged
Conversation
sentry-junior
Bot
force-pushed
the
feat/service-account-token-revoke
branch
from
November 9, 2026 21:01
7c3e296 to
f8669d5
Compare
Contributor
|
🚨 Warning: This pull request contains Frontend and Backend changes! It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently. Have questions? Please ask in the |
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 10, 2026 14:55
f8669d5 to
ece4c53
Compare
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 10, 2026 15:33
ece4c53 to
1331912
Compare
Contributor
Sentry Snapshot Testing
|
sentry-junior
Bot
force-pushed
the
feat/service-account-token-revoke
branch
from
November 10, 2026 15:46
1331912 to
7493553
Compare
skaasten
pushed a commit
that referenced
this pull request
Nov 10, 2026
#130483) Adds a new endpoint for listing service account tokens, gated by service account feature flag ------ Adds `GET /organizations/{org}/service-accounts/{id}/tokens/`. This is the first of the small token endpoint PRs, one per endpoint. It's stacked on #130456, which adds token generation: 1. **List tokens (this PR)** 2. Create a token: #130507 3. Revoke a token: #130508 The endpoint runs in control, is private, and is gated on `organizations:service-accounts` like the other service account endpoints. It needs `service_account:read`. It returns the account's active tokens, newest first: `id`, `name`, `scopes`, `tokenLastCharacters`, `dateCreated`, `dateLastUsed`, `dateExpires`. Revoked tokens aren't listed, and the response never includes a token's value. Pagination works the same way as the service account list. The account in the URL must be active and in the URL's org, otherwise it returns 404. Only a person signed in as themselves can call it. A service account gets 403 `user-required` (`requires_user`). The account lookup from the details endpoint moved into a shared `ServiceAccountScopedEndpointBase`. The details endpoint behaves the same. The new URL is added to `static/app/utils/api/knownSentryApiUrls.generated.ts`, so the frontend PR (#130485) can type-check against it. <!-- junior-request-attribution:start --> via @skaasten. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC0CA89MBDQB%3A1793911581.772709 --> -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC0CA89MBDQB%3A1793911581.772709) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC0CA89MBDQB%3A1793911581.772709/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 10, 2026 17:18
7493553 to
471213f
Compare
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 12, 2026 14:32
471213f to
94362a8
Compare
skaasten
marked this pull request as ready for review
November 12, 2026 14:33
gricha
approved these changes
Nov 12, 2026
markstory
reviewed
Nov 12, 2026
sentry-junior
Bot
force-pushed
the
feat/service-account-token-revoke
branch
2 times, most recently
from
November 12, 2026 17:53
ed0cd62 to
7a7c2fd
Compare
sentry-junior
Bot
force-pushed
the
feat/service-account-token-revoke
branch
from
November 12, 2026 17:58
7a7c2fd to
31c3901
Compare
markstory
approved these changes
Nov 12, 2026
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 13, 2026 14:19
31c3901 to
e5e709a
Compare
skaasten
pushed a commit
that referenced
this pull request
Nov 13, 2026
…ens (#130507) Adds a new endpoint for creating a service account access token We ensure the scopes for the token do not exceed the scopes for the current user. ------------------ Adds `POST /organizations/{org}/service-accounts/{id}/tokens/`. This is the second of three small token endpoint PRs, one per endpoint: 1. List tokens: #130483 2. **Create a token (this PR)** 3. Revoke a token: #130508 The endpoint needs `service_account:write` and takes `{name, scopes, expiresAt}`: - Each scope must already be granted to the account, otherwise it returns 400 with an error on `scopes`. - `expiresAt` defaults to 30 days from now (`DEFAULT_TOKEN_LIFETIME`). A value you set must be in the future, otherwise it returns 400 with an error on `expiresAt`. Send `null` for a token that never expires. - The token is generated with `generate_token(org slug, cell locality URL)`, using the same cell URL lookup as org auth tokens. Only the hash and the last 4 characters are stored. - The 201 response is the list item plus `token`. It's the only response that includes the token value. Seer agent tokens and impersonation sessions can't create tokens (`DisallowAgentTokenWrites`, `DisallowImpersonatedTokenCreation`). A service account gets 403 `user-required`, the same as on the list endpoint. Each create writes a `SERVICE_ACCOUNT_TOKEN_ADD` (226) audit log entry. Its data holds the token name, scopes and account, never the token or its hash. Review notes: - This stores `date_expires` but doesn't enforce it. Nothing authenticates service account tokens on requests yet, so the authenticator that comes later must reject a token once `date_expires` has passed. - `ServiceAccountTokenEndpointBase.permission_classes` is a longer tuple than its parent's, so `OrganizationServiceAccountEndpointBase.permission_classes` now has an explicit `tuple[type[BasePermission], ...]` type for mypy. <!-- junior-request-attribution:start --> via @skaasten. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:slack%3AC0CA89MBDQB%3A1793911581.772709 --> -- [View Session](https://junior-prod.sentry.dev/conversations/slack%3AC0CA89MBDQB%3A1793911581.772709) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC0CA89MBDQB%3A1793911581.772709/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 13, 2026 15:22
e5e709a to
3cb50c1
Compare
DELETE /organizations/{org}/service-accounts/{id}/tokens/{token_id}/ marks the
token deactivated. It stops working once the change replicates to the org's
cell. Each revoke writes a SERVICE_ACCOUNT_TOKEN_REMOVE audit log entry.
#130265 replicates service account tokens through their outbox, so the revoke test can check the cell replica's date_deactivated instead of asserting that the replication handler ran. Co-Authored-By: Shaun Kaasten <900809+skaasten@users.noreply.github.com>
The revocation is committed before replication, so a failed flush shouldn't turn a successful request into a 500; the outbox drains later. Also use get() instead of first() to avoid an ORDER BY on the lookup.
Revocation should still flush to the cell right away, but a replication failure shouldn't fail a revocation that's already committed. Catch OutboxDatabaseError and OutboxFlushError after the save, like ApiTokensEndpoint.post. The revoke test no longer wraps the request in outbox_runner, so it checks the flush.
skaasten
force-pushed
the
feat/service-account-token-revoke
branch
from
November 13, 2026 15:38
3cb50c1 to
5871e01
Compare
skaasten
enabled stacked auto-merge (squash)
November 13, 2026 15:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
DELETE /organizations/{org}/service-accounts/{id}/tokens/{token_id}/. This is the last of three small token endpoint PRs, one per endpoint:The endpoint needs
service_account:write. It locks the token row, setsdate_deactivatedand saves it, so the token's outbox updates the replica in the org's cell. It returns 204. The token must belong to the account in the URL, and the account must be active and in the URL's org. Otherwise, or if the token is already revoked, it returns 404.It uses the same guards as create: only a person signed in as themselves can call it, and Seer agent tokens and impersonation sessions are refused.
Each revoke writes a
SERVICE_ACCOUNT_TOKEN_REMOVE(227) audit log entry. Its data holds the token name, scopes and account, never the token or its hash.The settings UI's Revoke button in #130485 calls this endpoint.
Tests: the revoke test drains the token's outbox and checks that the cell's
ServiceAccountTokenReplicagets the token's newdate_deactivated. This works because #130265, the token replication handler, is merged.via @skaasten.
--
View Session [Sentry]