Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions backend/docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,44 @@ curl -X POST https://api.quicklendx.com/api/v1/keys/key_abc123/rotate \
4. Rotation event is logged in audit trail
5. Update your services with the new key

### Signing Secret Rotation (Grace Window)

If you cannot update all services simultaneously and need to avoid downtime, you can rotate the signing secret while keeping the same key ID. This allows the old secret to remain valid for a configurable grace window (default 24 hours).

#### Rotation Endpoint

```
POST /api/v1/keys/:id/rotate-signing-secret
```

#### Request Body

```json
{
"actor": "admin-user-id",
"grace_window_hours": 24
}
```

#### Response

```json
{
"data": {
"id": "key_abc123",
"key": "qlx_live_zzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"name": "Production Service Key",
"prefix": "qlx_live_xxxxx",
"scopes": ["read:users", "write:jobs"],
"created_at": "2026-04-29T10:00:00Z",
"prev_secret_expires_at": "2026-04-30T10:00:00Z",
"warning": "Store this new secret securely. The old secret will expire after the grace window."
}
}
```

**Note**: Rotating the signing secret a second time before the grace window expires will immediately invalidate the first old secret.

## Key Management Operations

### List All Keys
Expand Down
1 change: 1 addition & 0 deletions backend/docs/security-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ below apply to the admin plane and any future authenticated endpoints.
| 2.5 | Path parameters validated before use | ⚠️ | `src/controllers/v1/invoices.ts` | IDs are compared by equality only; add format validation (hex prefix, length) for production |
| 2.6 | No `eval`, `Function()`, or dynamic code execution on user input | ✅ | All controllers | Verified by code review |
| 2.7 | No direct string interpolation of user input into queries or shell commands | ✅ | All controllers | Mock data layer; enforce with parameterised queries when a real DB is added |
| 2.8 | Zod schema property-based fuzz testing | ✅ | `src/tests/validators.fuzz.test.ts` | All exported validators must be fuzzed against malformed payloads (NaN, deep nesting, prototype pollution) using fast-check |

---

Expand Down
67 changes: 67 additions & 0 deletions backend/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,73 @@ paths:
'500':
description: Internal server error

/keys/{id}/rotate-signing-secret:
post:
summary: Rotate an API key's signing secret
description: |
Generates a new signing secret for the specified API key ID, retaining the old secret for a configurable grace window (default 24h). Requires admin RBAC privileges.
security:
- BearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
description: The ID of the API key to rotate
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- actor
properties:
actor:
type: string
description: Identifier of the admin performing the rotation
grace_window_hours:
type: integer
default: 24
description: Hours to retain the old secret
responses:
"200":
description: Successfully rotated the signing secret
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
key:
type: string
description: The new plaintext key. Only returned once.
prefix:
type: string
scopes:
type: array
items:
type: string
created_at:
type: string
format: date-time
prev_secret_expires_at:
type: string
format: date-time
warning:
type: string
"400":
description: Validation error or failure to rotate
"401":
$ref: '#/components/responses/Unauthorized'
"403":
description: Insufficient admin role permissions

components:
securitySchemes:
BearerAuth:
Expand Down
Loading
Loading