feat(credentials): record, show, and explain saved token expiry
Merged
WA
3d ago+983+983+012345678901234567890123456789-7-7-0123456789
Summary
- When a GitHub or GitLab saved token is verified, Sim now records its expiry. The value goes on the existing
credential.access_token_expires_atcolumn. An expiry that has already passed contradicts the vendor accepting the token, so it is dropped. An imminent one is kept.- GitLab sends
expires_atfrom/personal_access_tokens/self, and the token stops working at midnight UTC on that date (GitLab docs). - GitHub sends the
GitHub-Authentication-Token-Expirationheader (changelog). GitHub does not document the header's value format. The reader accepts the two forms seen in the API responses that GitHub's own octokit.rb records: a numeric offset (2023-04-19 16:34:22 -0700) and aUTCmarker (2022-09-20 23:45:13 UTC). It leaves any other form unread. In that case the token is still accepted, just with no expiry recorded.
- GitLab sends
- Reconnecting replaces the stored expiry. A replacement token with no expiry clears it.
- The workspace and organization credential presentations report an optional
expiresAt. - The integrations list shows "expires in N days" or "expired". The credential detail page shows the expiry date and time in UTC.
- When a provider sends a 401 for a saved token whose recorded expiry has passed, the run says the token expired on
<date> UTCand asks for a new one. The provider's status and body are kept, and the refusal stays attributed to the provider in logs. A token is never refused on the recorded expiry alone. - The GitLab personal-token path reads
expires_atwith the same strict parser. - Not included:
- Slack token rotation. Slack refresh tokens are single-use and the old one is revoked after an unquantified grace period (Slack docs). Rotation needs a new provider id with durable, compare-and-swap rotating-chain storage, plus a live proof. The design is written up separately. The existing
xoxe.refusal stays. - Atlassian expiry. API tokens get a 1–365 day expiry when they are created, but Atlassian documents no way for a token to read its own expiry (Atlassian docs).
- Infisical expiry. The TTL is returned only by the login that issues the token, and a pasted token has no documented self-lookup (Infisical token auth).
- Expiry notifications. No credential notification system exists yet.
- Slack token rotation. Slack refresh tokens are single-use and the old one is revoked after an unquantified grace period (Slack docs). Rotation needs a new provider id with durable, compare-and-swap rotating-chain storage, plus a live proof. The design is written up separately. The existing
Deploy notes
- No schema change or migration.
- During the rolling deploy, an old worker that handles a reconnect of a GitHub/GitLab saved token updates only the secret. If a new worker had already recorded an expiry on that row, the old token's expiry stays until the next reconnect on new code, and the list and run error may show that stale date. Hitting this needs a create and a reconnect of the same credential inside the rollout window. Nothing is refused because of it.
Type of Change
- Bug fix
- New feature
- Breaking change
- Documentation
- Other: ___________
Testing
- Unit tests (written red first) cover:
- the GitHub and GitLab expiry parsers, including imminent expiries, both GitHub header forms, and other zone names being left unread
- the expiry caption
- the expired-token explanation in
executeTool, for external requests and internal operations
- A real-Postgres integration test,
saved-token-expiry.integration.ts, covers create, list, reconnect replace/clear, org-owned credentials, and the expired-token message. - Revert proofs turned the attribution and org-presentation tests red.
- What to review closely: the compatibility story. No schema change, no change to stored secret shapes, and the contract field is optional. Credentials saved before this have NULL expiry and behave exactly as before.
Checklist
- Code follows project style guidelines
- Self-reviewed my changes
- Tests added/updated and passing
- No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)