feat(cli)!: expire tokens by default (#21783)

## Summary

> NOTE: Calling this out as a breaking change in case existing consumers
of the CLI depend on being able to see expired tokens OR being able to
delete tokens immediately.

Updates the `coder tokens rm` command to immediately expire a token by
ID, preserving the token record for audit trail purposes. Tokens can
still be deleted by passing `--delete`.

## Problem

During an incident on dev.coder.com, operators needed to urgently expire
an API key that was stuck in a hot loop. The only way to do this was via
direct database access:

```sql
UPDATE api_keys SET expires_at = NOW() WHERE id = '...';
```

This is not ideal for operators who may not have direct DB access or
want to avoid manual SQL.

## Solution

This PR adds:

- **API endpoint**: `PUT /api/v2/users/{user}/keys/{keyid}/expire` -
Sets the token's `expires_at` to now
- **SDK method**: `ExpireAPIKey(ctx, userID, keyID)` 
- **Updates CLI**: `coder tokens rm <name|id|token>` now _expires_ by
default. You can still delete by passing the `--delete` flag. The `coder
tokens list` command now also hides expired tokens by default. You can
`--include-expired` if needed to include them.
- **Audit logging**: The expire action is logged with old and new key
states

## Test plan

- Tests cover: owner expiring own token, admin expiring other user's
token, non-admin cannot expire other's token, 404 for non-existent token

Closes #21782

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cian Johnston
2026-02-17 13:16:46 +00:00
committed by GitHub
co-authored by Claude Opus 4.5
parent a5f3acac2f
commit 4a3304fc38
19 changed files with 645 additions and 58 deletions
+34
View File
@@ -1015,6 +1015,40 @@ curl -X DELETE http://coder-server:8080/api/v2/users/{user}/keys/{keyid} \
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Expire API key
### Code samples
```shell
# Example request using curl
curl -X PUT http://coder-server:8080/api/v2/users/{user}/keys/{keyid}/expire \
-H 'Accept: */*' \
-H 'Coder-Session-Token: API_KEY'
```
`PUT /users/{user}/keys/{keyid}/expire`
### Parameters
| Name | In | Type | Required | Description |
|---------|------|----------------|----------|----------------------|
| `user` | path | string | true | User ID, name, or me |
| `keyid` | path | string(string) | true | Key ID |
### Example responses
> 404 Response
### Responses
| Status | Meaning | Description | Schema |
|--------|----------------------------------------------------------------------------|-----------------------|--------------------------------------------------|
| 204 | [No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5) | No Content | |
| 404 | [Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4) | Not Found | [codersdk.Response](schemas.md#codersdkresponse) |
| 500 | [Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1) | Internal Server Error | [codersdk.Response](schemas.md#codersdkresponse) |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
## Get user login type
### Code samples
+1 -1
View File
@@ -41,4 +41,4 @@ Tokens are used to authenticate automated clients to Coder.
| [<code>create</code>](./tokens_create.md) | Create a token |
| [<code>list</code>](./tokens_list.md) | List tokens |
| [<code>view</code>](./tokens_view.md) | Display detailed information about a token |
| [<code>remove</code>](./tokens_remove.md) | Delete a token |
| [<code>remove</code>](./tokens_remove.md) | Expire or delete a token |
+8
View File
@@ -23,6 +23,14 @@ coder tokens list [flags]
Specifies whether all users' tokens will be listed or not (must have Owner role to see all tokens).
### --include-expired
| | |
|------|-------------------|
| Type | <code>bool</code> |
Include expired tokens in the output. By default, expired tokens are hidden.
### -c, --column
| | |
+18 -2
View File
@@ -1,7 +1,7 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# tokens remove
Delete a token
Expire or delete a token
Aliases:
@@ -11,5 +11,21 @@ Aliases:
## Usage
```console
coder tokens remove <name|id|token>
coder tokens remove [flags] <name|id|token>
```
## Description
```console
Remove a token by expiring it. Use --delete to permanently hard-delete the token instead.
```
## Options
### --delete
| | |
|------|-------------------|
| Type | <code>bool</code> |
Permanently delete the token instead of expiring it. This removes the audit trail.