diff --git a/CHANGELOG.md b/CHANGELOG.md index f4fe91e..5736b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to this project will be documented here. The format roughly follows [Keep a Changelog](https://keepachangelog.com/) and the project uses [Semantic Versioning](https://semver.org/). +## [0.5.1] - 2026-05-11 + +### Fixed + +- **Auto-refresh of the access token now actually works.** The refresh endpoint reads the refresh JWT from a `refresh-token` HTTP header, not a JSON body — sending it as a body returned a pydantic 422 silently swallowed by `tryRefresh()`, which then propagated as an unauthenticated state to the caller. With this fix the CLI / library stays signed in for as long as the refresh token is valid (currently ~30 days), instead of forcing a manual `flappie login` after the ~12-hour access-token expiry. + +### Changed + +- `CLOUD_API.md` and `openapi.yaml` updated to document the refresh endpoint's actual header-based contract. + ## [0.5.0] - 2026-05-11 Docs-and-DX release: same wire surface as 0.4.0, but the package is meaningfully easier to discover and use. @@ -39,5 +49,6 @@ First public release on npm. - `RE.md` reverse-engineering playbook for when the vendor's mobile app updates. - Unit tests for pure CLI helpers (`parseBool`, `normalizePolicy`, `parseWeekdays`, `summarizeSettings`). +[0.5.1]: https://github.com/ooswald/flappie-api/releases/tag/v0.5.1 [0.5.0]: https://github.com/ooswald/flappie-api/releases/tag/v0.5.0 [0.4.0]: https://github.com/ooswald/flappie-api/releases/tag/v0.4.0 diff --git a/CLOUD_API.md b/CLOUD_API.md index aa604af..be0009e 100644 --- a/CLOUD_API.md +++ b/CLOUD_API.md @@ -55,7 +55,7 @@ Do not "fix" this — it matches what the backend actually accepts. | Method | Path | Body | CLI | |--------|------|------|-----| | POST | `/api/v1/users/login` | `{ email, password }` | ✅ `flappie login` | -| POST | `/api/v1/users/refresh` | `{ refresh_token }` | ✅ (auto on 401) | +| POST | `/api/v1/users/refresh` | (header `refresh-token: `, no body) | ✅ (auto on 401) | | POST | `/api/v1/users/validate-email` | `{ email }` | 🟡 | | POST | `/api/v1/users/reset-password` | `{ email }` | 🟡 | | POST | `/api/v1/users/reset-password/confirm-code` | `{ email, code }` | 🟡 | diff --git a/openapi.yaml b/openapi.yaml index da93a36..c3afa18 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -60,16 +60,16 @@ paths: post: tags: [auth] summary: Trade a refresh token for a new access token + description: | + The refresh token is sent as a request **header** (`refresh-token: `), + not in the JSON body. Sending it as a body field returns a pydantic 422. security: [] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [refresh_token] - properties: - refresh_token: { type: string } + parameters: + - in: header + name: refresh-token + required: true + schema: { type: string } + description: The refresh JWT from a prior login. responses: "200": description: New token pair diff --git a/package-lock.json b/package-lock.json index 33a130e..7c77843 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "flappie-cli", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "flappie-cli", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "commander": "^13.0.0" }, diff --git a/package.json b/package.json index 1aac994..e37af9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "flappie-api", - "version": "0.5.0", + "version": "0.5.1", "description": "Control your Flappie cat door from Node or the terminal. Typed TypeScript client with full CRUD for time plans, settings, and prey events. Unofficial, not associated with Flappie Technologies AG.", "type": "module", "license": "MIT", diff --git a/src/client.ts b/src/client.ts index d3825b3..1666467 100644 --- a/src/client.ts +++ b/src/client.ts @@ -153,14 +153,22 @@ export class FlappieClient { return data as T; } - /** Try to refresh the access token. Returns true on success. */ + /** + * Try to refresh the access token. Returns true on success. + * + * The Flappie backend reads the refresh token from the `refresh-token` + * HTTP header (not a JSON body) - sending it in the body returns a + * pydantic 422 about a missing header field. + */ async tryRefresh(): Promise { if (!this.auth.refresh_token) return false; try { const res = await this.fetchFn(`${this.baseUrl}/api/v1/users/refresh`, { method: "POST", - headers: { "Content-Type": "application/json", "Accept": "application/json" }, - body: JSON.stringify({ refresh_token: this.auth.refresh_token }), + headers: { + "Accept": "application/json", + "refresh-token": this.auth.refresh_token, + }, }); if (!res.ok) return false; const data = (await res.json()) as Partial & { accessToken?: string; refreshToken?: string };