mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
Deleting a user's stored Oura BYO credentials drops a live connection (a token minted against the deleted app is orphaned), but the credentials DELETE route skipped the auditLog + markDisconnected the disconnect route performs, leaving the integration ledger parked at its last state with no audit trail. Mirror the disconnect route when a token was present. Drop two unused classifier type aliases and unexport the internal invalid-grant helper, and refresh the stale 'full OAuth from env' headers on the connect route and the env-credentials helper to the v1.17.1 DB-first framing. Add the missing route tests for the BYO surface this release touched: credentials GET/PUT/DELETE (422 reject, encrypt-on-write, the audit/ledger branch) and the callback CSRF / cross-user / nocreds / success paths.
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
/**
|
|
* v1.17.0 (F4) — off-response classifier for Oura Cloud API v2 replies.
|
|
* HTTP-status-driven; the shared core lives in
|
|
* `src/lib/integrations/http-status-classifier.ts` and is re-exported here
|
|
* under the Oura-specific names callers already use.
|
|
*
|
|
* - `success` → HTTP 2xx
|
|
* - `transient` → 429 (rate-limited), 5xx, 3xx, unknown.
|
|
* - `reauth_required` → 401 / 403 — token rejected / grant revoked.
|
|
* - `persistent` → any other 4xx — malformed request / contract drift.
|
|
*/
|
|
|
|
import {
|
|
classifyHttpStatus,
|
|
classifyIntegrationError,
|
|
IntegrationApiError,
|
|
isOAuthInvalidGrant,
|
|
type ClassifiedHttpResponse,
|
|
type IntegrationClassification,
|
|
} from "@/lib/integrations/http-status-classifier";
|
|
|
|
export function classifyOuraResponse(
|
|
httpStatus: number,
|
|
): ClassifiedHttpResponse {
|
|
return classifyHttpStatus(httpStatus);
|
|
}
|
|
|
|
export class OuraApiError extends IntegrationApiError {
|
|
constructor(opts: {
|
|
verb: string;
|
|
classification: IntegrationClassification;
|
|
httpStatus: number | undefined;
|
|
reason: string;
|
|
upstreamError?: string;
|
|
}) {
|
|
super({ vendor: "Oura", ...opts });
|
|
this.name = "OuraApiError";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* True when a caught error is an OAuth `invalid_grant` on the token endpoint —
|
|
* the canonical revoked-refresh-token signal. Lifts a 400 carrying
|
|
* `invalid_grant` from `persistent` to `reauth_required` so the connection
|
|
* surfaces the reconnect prompt. Internal to `classifyOuraError`.
|
|
*/
|
|
function isInvalidGrant(err: unknown): boolean {
|
|
return isOAuthInvalidGrant(err, "Oura");
|
|
}
|
|
|
|
export function classifyOuraError(err: unknown): IntegrationClassification {
|
|
if (isInvalidGrant(err)) return "reauth_required";
|
|
return classifyIntegrationError(err, "Oura");
|
|
}
|