From 9a86a9427b69fb023ccd54877eaac65655ce2fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 11 Oct 2026 13:59:05 +0200 Subject: [PATCH] fix(oura): name the issuer refusal on the integrations page --- messages/de.json | 1 + messages/en.json | 1 + messages/es.json | 1 + messages/fr.json | 1 + messages/it.json | 1 + messages/ko.json | 1 + messages/pl.json | 1 + .../__tests__/oauth-return-outcome.test.ts | 33 +++++++++++++++++++ .../integrations/connections-panel.tsx | 14 +++++++- 9 files changed, 53 insertions(+), 1 deletion(-) diff --git a/messages/de.json b/messages/de.json index 60d3a4dfa..31294658e 100644 --- a/messages/de.json +++ b/messages/de.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura ist auf diesem Server nicht konfiguriert. Bitte deinen Administrator, die Oura-OAuth-Zugangsdaten zu hinterlegen.", "token": "Der Token-Austausch mit Oura ist fehlgeschlagen. Bitte starte die Verbindung erneut.", "rate_limited": "Zu viele Verbindungsversuche. Warte einen Moment und versuche es erneut.", + "issuer": "Die Anmeldeantwort kam nicht vom Autorisierungsserver von Oura, deshalb wurde die Verbindung zu deiner Sicherheit abgebrochen. Bitte starte die Verbindung erneut. Wenn das wieder passiert, sag deinem Administrator Bescheid.", "generic": "Unbekannter Fehler bei der Verbindung. Bitte starte die Verbindung erneut." }, "integrationSetupGuide": "Einrichtungsanleitung", diff --git a/messages/en.json b/messages/en.json index 6e22ad25f..ba3a1b552 100644 --- a/messages/en.json +++ b/messages/en.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura is not configured on this server. Ask your administrator to add the Oura OAuth credentials.", "token": "The token exchange with Oura failed. Please start the connection again.", "rate_limited": "Too many connection attempts. Wait a moment and try again.", + "issuer": "The sign-in answer did not come from Oura's own authorization server, so the connection was stopped for your safety. Please start the connection again. If this keeps happening, tell your administrator.", "generic": "Unknown error while connecting. Please start the connection again." }, "integrationSetupGuide": "Setup guide", diff --git a/messages/es.json b/messages/es.json index 93c709eab..e58bb81a6 100644 --- a/messages/es.json +++ b/messages/es.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura no está configurado en este servidor. Pide a tu administrador que añada las credenciales OAuth de Oura.", "token": "El intercambio de token con Oura falló. Inicia la conexión de nuevo.", "rate_limited": "Demasiados intentos de conexión. Espera un momento e inténtalo de nuevo.", + "issuer": "La respuesta de inicio de sesión no procedía del servidor de autorización de Oura, así que la conexión se detuvo por tu seguridad. Vuelve a iniciar la conexión. Si sigue ocurriendo, avisa a tu administrador.", "generic": "Error desconocido al conectar. Inicia la conexión de nuevo." }, "integrationSetupGuide": "Guía de configuración", diff --git a/messages/fr.json b/messages/fr.json index b477b0f18..fbbcd93e7 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura n'est pas configuré sur ce serveur. Demandez à votre administrateur d'ajouter les identifiants OAuth Oura.", "token": "L'échange de jeton avec Oura a échoué. Veuillez relancer la connexion.", "rate_limited": "Trop de tentatives de connexion. Patientez un instant et réessayez.", + "issuer": "La réponse de connexion ne provenait pas du serveur d'autorisation d'Oura, la connexion a donc été interrompue par sécurité. Veuillez relancer la connexion. Si cela se reproduit, prévenez votre administrateur.", "generic": "Erreur inconnue lors de la connexion. Veuillez relancer la connexion." }, "integrationSetupGuide": "Guide de configuration", diff --git a/messages/it.json b/messages/it.json index 172e52d8a..e33119da0 100644 --- a/messages/it.json +++ b/messages/it.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura non è configurato su questo server. Chiedi al tuo amministratore di aggiungere le credenziali OAuth di Oura.", "token": "Lo scambio del token con Oura è fallito. Riavvia la connessione.", "rate_limited": "Troppi tentativi di connessione. Attendi un momento e riprova.", + "issuer": "La risposta di accesso non proveniva dal server di autorizzazione di Oura, quindi la connessione è stata interrotta per la tua sicurezza. Avvia di nuovo la connessione. Se succede ancora, avvisa il tuo amministratore.", "generic": "Errore sconosciuto durante la connessione. Riavvia la connessione." }, "integrationSetupGuide": "Guida alla configurazione", diff --git a/messages/ko.json b/messages/ko.json index 02b93b9f5..6c81ce563 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -6832,6 +6832,7 @@ "nocreds": "이 서버에는 Oura가 설정돼 있지 않아요. 관리자에게 Oura OAuth 자격 증명을 추가해 달라고 요청하세요.", "token": "Oura와의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", "rate_limited": "연결 시도가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "issuer": "로그인 응답이 Oura의 인증 서버에서 오지 않아서 안전을 위해 연결을 중단했어요. 연결을 다시 시작해 주세요. 계속 발생하면 관리자에게 알려 주세요.", "generic": "연결 중 알 수 없는 오류가 났어요. 연결을 다시 시작해 주세요." }, "integrationSetupGuide": "설정 가이드", diff --git a/messages/pl.json b/messages/pl.json index 76f37282e..864d500b4 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -6832,6 +6832,7 @@ "nocreds": "Oura nie jest skonfigurowana na tym serwerze. Poproś administratora o dodanie danych OAuth Oura.", "token": "Wymiana tokena z Oura nie powiodła się. Rozpocznij połączenie ponownie.", "rate_limited": "Zbyt wiele prób połączenia. Poczekaj chwilę i spróbuj ponownie.", + "issuer": "Odpowiedź logowania nie pochodziła z serwera autoryzacji Oura, więc połączenie zostało przerwane dla Twojego bezpieczeństwa. Rozpocznij połączenie ponownie. Jeśli to się powtarza, powiadom administratora.", "generic": "Nieznany błąd podczas łączenia. Rozpocznij połączenie ponownie." }, "integrationSetupGuide": "Przewodnik konfiguracji", diff --git a/src/components/settings/__tests__/oauth-return-outcome.test.ts b/src/components/settings/__tests__/oauth-return-outcome.test.ts index 5a607a881..e768a03dd 100644 --- a/src/components/settings/__tests__/oauth-return-outcome.test.ts +++ b/src/components/settings/__tests__/oauth-return-outcome.test.ts @@ -61,9 +61,42 @@ describe("oauthReasonKey", () => { ); }); + it("names Oura's issuer refusal with its own copy, and only for Oura", () => { + expect(oauthReasonKey("oura", "issuer")).toBe( + "settings.ouraOauthError.issuer", + ); + // No other callback emits it, and no other subtree carries the copy. + expect(oauthReasonKey("polar", "issuer")).toBe( + "settings.polarOauthError.generic", + ); + }); + it("falls back to generic for an unknown tag", () => { expect(oauthReasonKey("whoop", "totally_unknown")).toBe( "settings.whoopOauthError.generic", ); }); }); + +describe("the Oura callback's reasons", () => { + it("each has its own copy, none falls back to generic", async () => { + const { readFileSync } = await import("node:fs"); + const { join } = await import("node:path"); + const route = readFileSync( + join(process.cwd(), "src/app/api/oura/callback/route.ts"), + "utf8", + ); + const reasons = [...route.matchAll(/ERR\("([a-z_0-9]+)"\)/g)].map( + (m) => m[1], + ); + expect(reasons).toContain("issuer"); + const en = JSON.parse( + readFileSync(join(process.cwd(), "messages/en.json"), "utf8"), + ) as { settings: { ouraOauthError: Record } }; + for (const reason of reasons) { + const key = oauthReasonKey("oura", reason); + expect(key, reason).toBe(`settings.ouraOauthError.${reason}`); + expect(en.settings.ouraOauthError[reason], reason).toBeTruthy(); + } + }); +}); diff --git a/src/components/settings/integrations/connections-panel.tsx b/src/components/settings/integrations/connections-panel.tsx index 89dbfeb89..52877436f 100644 --- a/src/components/settings/integrations/connections-panel.tsx +++ b/src/components/settings/integrations/connections-panel.tsx @@ -127,6 +127,17 @@ const OAUTH_OUTCOME_REASONS = new Set([ "expired", ]); +/** + * Reason tags only one provider's callback emits, with copy under that + * provider's subtree alone. Oura's `issuer`: the authorization response named + * an issuer that is not Oura's (RFC 9207), so the callback refused the code. + */ +const PROVIDER_ONLY_REASONS: Partial< + Record> +> = { + oura: new Set(["issuer"]), +}; + type OAuthOutcome = | { provider: OAuthOutcomeProvider; kind: "connected" } | { provider: OAuthOutcomeProvider; kind: "error"; reason: string }; @@ -162,7 +173,8 @@ export function oauthReasonKey( reason: string, ): string { const base = OAUTH_ERROR_KEY_BASE[provider]; - return OAUTH_OUTCOME_REASONS.has(reason) + return OAUTH_OUTCOME_REASONS.has(reason) || + PROVIDER_ONLY_REASONS[provider]?.has(reason) ? `${base}.${reason}` : `${base}.generic`; }